package data

import (
	"context"
	"errors"
	"fmt"
	tw "gitlab.cqcb.com/shangyou_mic/dscm-approve-api-pb/api/account/times_words"
	dass "gitlab.cqcb.com/shangyou_mic/dscm-approve-api-pb/api/dscm-approve-system-service/v1"
	daas "gitlab.cqcb.com/shangyou_mic/dscm-approve-api-pb/api/dscm_approve_article_service/v1"
	"gorm.io/gorm/clause"
	v1 "gitlab.cqcb.com/shangyou_mic/dscm-approve-api-pb/api/roses/v1"
	"rosesservice/internal/biz"
	"rosesservice/internal/crontab"
	"rosesservice/pkg/util"
	"strings"
	"sync"
	"time"

	"github.com/go-kratos/kratos/v2/log"
	"gorm.io/gorm"
)

const (
	ThisMonthScore = "this_month_score" // 本月剩余总积分
)

type Articles struct {
	grabArticles     sync.Mutex // 审读稿件抢单锁
	withdrawIntegral sync.Mutex // 账户积分提现锁
	// changeUserIntegral sync.Mutex // 更新账户积分锁
	db      *DB
	logger  log.Logger
	basic   *Basic
	crontab *crontab.Crontab

	articleRecords  tw.TimesRecordsClient
	articleSource   dass.SourceClient
	articleMedia    dass.MediaClient
	articlePlatform dass.PlatformClient
	articleTopic    daas.TopicClient
	articleArticle  daas.ContentClient
}

func NewArticles(
	db *DB,
	logger log.Logger,
	basic *Basic,
	crontab *crontab.Crontab,
	grpcClient *GRPCClient,
) (biz.ArticlesRepo, error) {
	result := &Articles{
		db:      db,
		logger:  logger,
		basic:   basic,
		crontab: crontab,
	}
	{
		result.articleRecords = tw.NewTimesRecordsClient(grpcClient.articleRecords)
		result.articleSource = dass.NewSourceClient(grpcClient.articleSource)
		result.articleMedia = dass.NewMediaClient(grpcClient.articleMedia)
		result.articlePlatform = dass.NewPlatformClient(grpcClient.articlePlatform)
		result.articleTopic = daas.NewTopicClient(grpcClient.articleTopic)
		result.articleArticle = daas.NewContentClient(grpcClient.articleArticle)
	}
	err := result.init()
	if err != nil {
		return nil, err
	}
	err = result.initThisMonthScore()
	if err != nil {
		return nil, err
	}
	return result, nil
}

func (s *Articles) init() error {
	if err := s.timeout(); err != nil {
		return err
	}
	return nil
}

type MyContent string

const (
	ArticleClosedUpdateUserIntegral MyContent = "article_closed"
)

func (s *Articles) ReceiveArticle(ctx context.Context, req *v1.ArticleReceive) (*v1.ArticleReceiveReply, error) {
	db := s.db.DB()
	articles := make([]*Article, 0)
	err := db.Model(&Article{}).
		Where("article_id = ?", req.Id).
		Where("deleted_at = 0").
		Select("id").
		Limit(1).
		Find(&articles).
		Error
	if err != nil {
		return nil, err
	}
	if len(articles) == 1 {
		// 稿件已存在
		return &v1.ArticleReceiveReply{}, nil
	}
	// 去获取稿件数据
	articleReply, err := s.articleArticle.GetArticle(ctx, &daas.BatchGetArticleRequest{
		ArticleIds: []uint64{uint64(req.Id)},
	})
	if err != nil {
		return nil, err
	}
	if len(articleReply.Articles) == 0 {
		return nil, errors.New("no data")
	}
	data := articleReply.Articles[0]
	hours, err := s.basic.getCfgInt("B00")
	if err != nil {
		return nil, err
	}
	if hours <= 0 {
		hours = 12
	}
	timestamp := util.Timestamp()
	article := &Article{
		Title:      data.Title,
		Content:    &data.Content,
		SourceId:   int64(data.SourceId),
		Keyword:    data.Keyword,
		Category:   1,
		State:      1,
		Claimed:    0,
		PublishAt:  int64(data.PublishTime),
		DeadlineAt: timestamp + hours*3600,
		Editor:     data.Editor,
		// EditorCharge:     data.EditorCharge,
		// EditorMain:       data.EditorMain,
		SourceUrl:    &data.Url,
		CategoryType: req.CategoryType,
		AutoCheck:    int32(data.AutoCheck),
		ManualCheck:  int32(data.ManualCheck),
		// ManualCheckVideo: data.ManualCheckVideo,
		// ManualCheckImage: data.ManualCheckImage,
		ArticleId: req.Id,
		SyncError: 0,
	}
	article.CreatedAt = timestamp
	article.UpdatedAt = timestamp
	err = db.Model(&Article{}).Create(article).Error
	if err != nil {
		return nil, err
	}
	return &v1.ArticleReceiveReply{}, nil
}

// pullReceiveArticleRecords 周期性同步稿件的差错数据
func (s *Articles) pullReceiveArticleRecords() {
	db := s.db.DB()
	articles := make([]*Article, 0)
	db.Model(&Article{}).
		Where("deleted_at = 0").
		Where("sync_error = 0").
		Where("article_id > 0").
		Select("id", "article_id").
		Order("id DESC").
		Limit(500).
		Find(&articles)
	if len(articles) == 0 {
		return
	}
	timestamp := util.Timestamp()
	fc := func(article *Article) {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
		defer func() {
			cancel()
		}()
		reply, err := s.articleRecords.TimesRecordsGetListByAid(ctx, &tw.TimesRecordsGetListRequest{
			Aid: article.ArticleId,
		})
		if err != nil {
			return
		}
		lists := reply.List
		length := len(lists)
		if length == 0 {
			return
		}
		creates := make([]*ArticleRecords, 0, length)
		createsMap := make(map[int64]*struct{}, length)
		for _, v := range lists {
			errorLocation := fmt.Sprintf("%d", v.Position)
			w := &ArticleRecords{
				FromType:             1,
				ErrorCategory:        v.CategoryId,
				ErrorUrls:            nil,
				ErrorLocation:        &errorLocation,
				ErrorType:            "",
				ErrorTypeInt:         0,
				ErrorSubtype:         "",
				ErrorReason:          "",
				ErrorMessage:         v.ExpSource,
				ErrorModifyAdvice:    &v.Propose,
				ErrorLevel:           v.RiskId,
				ErrorContent:         &v.Problem,
				ErrorVideoScreenshot: nil,
				Note:                 &v.Phrase,
				ConfidenceLevel:      0,
				ConclusionType:       v.IsWrong,
				Conclusion:           "",
				ArticleRecordsId:     int64(v.Id),
			}
			w.ArticleId = article.Id
			w.CreatedAt = timestamp
			w.UpdatedAt = timestamp
			if _, ok := createsMap[w.ArticleRecordsId]; ok {
				continue
			}
			createsMap[w.ArticleRecordsId] = nil
			creates = append(creates, w)
		}
		if len(creates) == 0 {
			return
		}
		_ = db.Transaction(func(tx *gorm.DB) error {
			err = tx.Model(&Article{}).
				Where("id = ?", article.Id).
				Updates(map[string]interface{}{
					"sync_error": 1,
					"updated_at": timestamp,
				}).Error
			if err != nil {
				return err
			}
			err = tx.Model(&ArticleRecords{}).Create(creates).Error
			if err != nil {
				return err
			}
			return nil
		})
	}
	for _, v := range articles {
		fc(v)
	}
}

// pullMediaPlatformChannel 同步信源数据
func (s *Articles) pullMediaPlatformChannel() {
	timestamp := util.Timestamp()
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
	defer func() {
		cancel()
	}()
	var sources []*dass.SourceData
	var media []*dass.MediaData
	var platform []*dass.PlatformData
	sourcesMap := make(map[int64]*dass.SourceData)
	mediaMap := make(map[int64]*dass.MediaData)
	platformMap := make(map[int64]*dass.PlatformData)
	wg := sync.WaitGroup{}
	wg.Add(1)
	go func() {
		defer wg.Done()
		reply, err := s.articleSource.GetAllSource(ctx, nil)
		if err != nil {
			return
		}
		sources = reply.Sources
		for _, v := range sources {
			sourcesMap[int64(v.Id)] = v
		}
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		reply, err := s.articleMedia.AllMedia(ctx, &dass.AllMediaRequest{})
		if err != nil {
			return
		}
		media = reply.Medium
		for _, v := range media {
			mediaMap[int64(v.Id)] = v
		}
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		reply, err := s.articlePlatform.AllPlatform(ctx, &dass.AllPlatformRequest{})
		if err != nil {
			return
		}
		platform = reply.Platforms
		for _, v := range platform {
			platformMap[int64(v.Id)] = v
		}
	}()
	wg.Wait()
	length := len(sources)
	if length == 0 {
		return
	}
	create := make([]*ApproveSources, 0, length)
	createMap := make(map[int64]*ApproveSources, length)
	for _, v := range sources {
		tmp := &ApproveSources{
			MediaId:            int64(v.MediaId),
			Media:              "",
			PlatformId:         int64(v.PlatformId),
			Platform:           "",
			ChannelName:        v.ChannelName,
			SourceLabel:        &v.SourceLabel,
			SourceType:         v.SourceType,
			Frequency:          v.Frequency,
			Level:              v.Level,
			AutoMachine:        v.AutoMachine,
			AutoMachineImage:   v.AutoMachineImage,
			AutoMachineVideo:   v.AutoMachineVideo,
			ManualMachine:      v.ManualMachine,
			ManualMachineImage: v.ManualMachineImage,
			ManualMachineVideo: v.ManualMachineVideo,
			Opened:             v.Opened,
		}
		tmp.Id = int64(v.Id)
		tmp.CreatedAt = timestamp
		tmp.UpdatedAt = timestamp
		if val, ok := mediaMap[tmp.MediaId]; ok {
			tmp.Media = val.Name
		}
		if val, ok := platformMap[tmp.PlatformId]; ok {
			tmp.Platform = val.Name
		}
		if _, ok := createMap[tmp.Id]; ok {
			continue
		}
		createMap[tmp.Id] = tmp
		create = append(create, tmp)
	}
	db := s.db.DB()
	handle := func(v *ApproveSources) {
		lists := make([]*ApproveSources, 0)
		db.Model(&ApproveSources{}).
			Where("id = ?", v.Id).
			Limit(1).
			Find(&lists)
		length = len(lists)
		if length == 0 {
			db.Model(&ApproveSources{}).Create(v)
			return
		}
		has := lists[0]
		m := make(map[string]any)
		if has.Media != v.Media {
			m["media"] = v.Media
		}
		if has.Platform != v.Platform {
			m["platform"] = v.Platform
		}
		if has.ChannelName != v.ChannelName {
			m["channel_name"] = v.ChannelName
		}
		if v.SourceLabel != nil {
			m["source_label"] = v.SourceLabel
		}
		if has.SourceType != v.SourceType {
			m["source_type"] = v.SourceType
		}
		if has.Frequency != v.Frequency {
			m["frequency"] = v.Frequency
		}
		if has.Level != v.Level {
			m["level"] = v.Level
		}
		if has.AutoMachine != v.AutoMachine {
			m["auto_machine"] = v.AutoMachine
		}
		if has.AutoMachineImage != v.AutoMachineImage {
			m["auto_machine_image"] = v.AutoMachineImage
		}
		if has.AutoMachineVideo != v.AutoMachineVideo {
			m["auto_machine_video"] = v.AutoMachineVideo
		}
		if has.ManualMachine != v.ManualMachine {
			m["manual_machine"] = v.ManualMachine
		}
		if has.ManualMachineImage != v.ManualMachineImage {
			m["manual_machine_image"] = v.ManualMachineImage
		}
		if has.ManualMachineVideo != v.ManualMachineVideo {
			m["manual_machine_video"] = v.ManualMachineVideo
		}
		if has.Opened != v.Opened {
			m["opened"] = v.Opened
		}
		if len(m) == 0 {
			return
		}
		m["updated_at"] = v.UpdatedAt
		db.Model(&ApproveSources{}).Where("id = ?", v.Id).Updates(m)
	}
	for _, v := range create {
		handle(v)
	}
}

// pullTopic 同步专题数据
func (s *Articles) pullTopic() {
	db := s.db.DB()
	timestamp := util.Timestamp()
	ctx, cancel := context.WithTimeout(context.Background(), time.Minute*30)
	defer func() {
		cancel()
	}()
	page := int32(1)
	pageSize := int32(500)
	for {
		reply, err := s.articleTopic.List(ctx, &daas.ListTopicRequest{
			Page:     page,
			PageSize: pageSize,
		})
		if err != nil {
			return
		}
		length := len(reply.Topic)
		if length == 0 {
			return
		}
		lists := make([]*Topic, 0, length)
		for _, v := range reply.Topic {
			w := &Topic{
				TopicId:   int64(v.Id),
				Title:     v.Title,
				Keyword:   v.Keyword,
				State:     v.Status,
				CreatedAt: timestamp,
			}
			lists = append(lists, w)
		}
		err = db.Clauses(clause.OnConflict{
			Columns:   []clause.Column{{Name: "topic_id"}},
			DoUpdates: clause.AssignmentColumns([]string{"title", "keyword", "state"}),
		}).Create(lists).Error
		if err != nil {
			s.logger.Log(log.LevelWarn, "error", err.Error())
			return
		}
		page++
	}
}

// updateUserIntegral 更新用户积分(业务允许扣除用户的积分余额度到负数)
func (s *Articles) updateUserIntegral(ctx context.Context, tx *gorm.DB, user *User, integrals ...*UserIntegral) error {
	timestamp := util.Timestamp()
	originIntegral := int64(0)
	latestIntegral := int64(0)
	thisMonthScore := int64(0)
	for i, v := range integrals {
		if v.CreatedAt <= 0 || v.UpdatedAt <= 0 {
			v.CreatedAt = timestamp
			v.UpdatedAt = timestamp
		} else {
			timestamp = v.CreatedAt
		}
		if i == 0 {
			originIntegral = v.ChangeBefore
		}
		latestIntegral = v.ChangeAfter
		if v.Category == 1 {
			thisMonthScore += v.ChangeAmount
		}
	}
	err := tx.Model(&UserIntegral{}).Create(integrals).Error
	if err != nil {
		return err
	}
	tmp := tx.Model(&User{}).
		Where("id = ? AND integral = ? AND version_id = ?", user.Id, originIntegral, user.VersionId).
		Updates(map[string]any{
			"integral":   latestIntegral,
			"version_id": user.VersionId + 1,
			"updated_at": timestamp,
		})
	if err = tmp.Error; err != nil {
		return err
	}
	if tmp.RowsAffected <= 0 {
		return errors.New("更新账户积分失败,请重试")
	}
	closed := ctx.Value(ArticleClosedUpdateUserIntegral)
	if closed != nil && thisMonthScore != 0 {
		prepare := fmt.Sprintf("update %s set run_val_int = run_val_int + ? where run_key = ?", (&RunValue{}).TableName())
		err = tx.Exec(prepare, thisMonthScore, ThisMonthScore).Error
		if err != nil {
			return err
		}
	}

	return nil
}

func (s *Articles) readerTimeoutHandle() {
	db := s.db.DB()
	timestamp := util.Timestamp()
	lists := make([]*ArticleReader, 0)
	err := db.Model(&ArticleReader{}).
		Where("deadline_at > 0 AND state = 1 AND deadline_at < ?", timestamp).
		Limit(1000).
		Find(&lists).
		Error
	if err != nil {
		s.logger.Log(log.LevelWarn, "error", err.Error())
		return
	}
	ids := make([]int64, 0)
	for _, list := range lists {
		ids = append(ids, list.Id)
	}
	// 获取配置扣除积分数
	integral := int64(5)
	{
		tmp, rer := s.basic.getCfgInt("B02")
		if rer != nil {
			s.logger.Log(log.LevelWarn, "error", rer.Error())
			return
		}
		integral = tmp
	}
	for _, v := range lists {
		_ = db.Transaction(func(tx *gorm.DB) error {
			users := make([]*User, 0)
			tx.Model(&User{}).Where("id = ?", v.ReaderId).Limit(1).Find(&users)
			if len(users) == 0 {
				return nil
			}
			user := users[0]
			err = tx.Model(&ArticleReader{}).
				Where("id = ? AND state = ?", v.Id, v.State).
				Updates(map[string]any{
					"state":      5,
					"note":       "审读超时",
					"updated_at": timestamp,
				}).Error
			if err != nil {
				return err
			}
			articles := make([]*Article, 0)
			tx.Model(&Article{}).Where("id = ?", v.ArticleId).Limit(1).Find(&articles)
			if len(articles) > 0 {
				first := articles[0]
				updates := map[string]any{}
				if first.State == 9 {
					updates["state"] = 1
				}
				if first.Claimed == 1 {
					updates["claimed"] = 0
				}
				if len(updates) > 0 {
					updates["updated_at"] = timestamp
					tx.Model(&Article{}).Where("id = ?", v.ArticleId).Updates(updates)
				}
			}
			// 需要扣除积分时
			if integral > 0 {
				userIntegral := &UserIntegral{
					AssocId:      v.Id,
					ChangeBefore: user.Integral,
					ChangeAmount: 0 - integral,
					ChangeAfter:  user.Integral - integral,
					State:        9,
					StateDesc:    "已完成",
				}
				userIntegral.Category = 1
				userIntegral.UserId = user.Id
				userIntegral.Note = "审读超时"
				userIntegral.CreatedAt = timestamp
				userIntegral.UpdatedAt = timestamp
				err = s.updateUserIntegral(context.Background(), tx, user, userIntegral)
				if err != nil {
					return err
				}
			}
			return nil
		})
	}
}

func (s *Articles) timeout() error {
	// 审读超时
	_, err := s.crontab.AddFunc("*/15 * * * * *", s.readerTimeoutHandle)
	if err != nil {
		return err
	}

	// 稿件自动结案
	_, err = s.crontab.AddFunc("*/15 * * * * *", s.articleAutoCloseHandle)
	if err != nil {
		return err
	}

	// 自动解冻被冻结的账户
	_, err = s.crontab.AddFunc("0 * * * * *", s.unfreeze)
	if err != nil {
		return err
	}

	// 自动解绑稿件与复核员的绑定
	_, err = s.crontab.AddFunc("*/15 * * * * *", s.postArticleVerifierUnlockArticleAuto)
	if err != nil {
		return err
	}

	// 稿件超时 状态自动流转到已超时状态
	_, err = s.crontab.AddFunc("*/7 * * * * *", s.articleDeadline)
	if err != nil {
		return err
	}

	// 根据稿件id获取稿件差错信息
	_, err = s.crontab.AddFunc("*/15 * * * * *", s.pullReceiveArticleRecords)
	if err != nil {
		return err
	}

	// 同步媒体平台等信息
	_, err = s.crontab.AddFunc("0 0 * * * *", s.pullMediaPlatformChannel)
	if err != nil {
		return err
	}

	// 同步专题等信息
	_, err = s.crontab.AddFunc("0 0 * * * *", s.pullTopic)
	if err != nil {
		return err
	}

	return nil
}

func (s *Articles) articleAutoCloseHandle() {
	db := s.db.DB()
	articles := make([]int64, 0)
	db.Model(&Article{}).
		Where("state = ? AND verifier_id = 0 AND deleted_at = 0", 9).
		Limit(1000).
		Order("id ASC").
		Select("id").
		Scan(&articles)
	for _, id := range articles {
		_ = s.AfterReaderCommit(id)
	}
}

// initThisMonthScore 初始化本月生于总积分
func (s *Articles) initThisMonthScore() error {
	db := s.db.DB()
	lists := make([]*RunValue, 0)
	tmp := db.Model(&RunValue{}).Where("run_key = ?", ThisMonthScore).Limit(1).Find(&lists)
	if err := tmp.Error; err != nil {
		return err
	}
	if len(lists) == 0 {
		scores, err := s.basic.getCfgInt("B0C")
		if err != nil {
			return err
		}
		if scores <= 0 {
			scores = 10000
		}
		create := &RunValue{
			RunKey:    ThisMonthScore,
			RunValInt: scores,
			Note:      "本月剩余可用积分",
		}
		timestamp := util.Timestamp()
		create.CreatedAt = timestamp
		create.UpdatedAt = timestamp
		err = db.Model(&RunValue{}).Create(create).Error
		if err != nil {
			return err
		}
	}
	// 每月更新一次
	if _, err := s.crontab.AddFunc("0 0 0 1 * *", func() {
		scores, err := s.basic.getCfgInt("B0C")
		if err != nil {
			scores = 10000
		}
		values := make([]*RunValue, 0)
		db.Model(&RunValue{}).Where("run_key = ?", ThisMonthScore).Select("id").Limit(1).Find(&values)
		if len(values) > 0 {
			db.Model(&RunValue{}).Where("id = ?", values[0].Id).Updates(map[string]any{
				"run_val_int": scores,
				"updated_at":  util.Timestamp(),
			})
		}
	}); err != nil {
		return err
	}
	return nil
}

// freeze 尝试冻结误判的用户
func (s *Articles) freeze(articleId int64) error {
	db := s.db.DB()

	misjudgedTimes, err := s.basic.getCfgInt("B03")
	if err != nil {
		return err
	}
	if misjudgedTimes <= 0 {
		return nil
	}
	freezeDays, err := s.basic.getCfgInt("B04")
	if err != nil {
		return err
	}
	if freezeDays <= 0 {
		return nil
	}

	now := time.Now()
	monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.Local)

	lists := make([]*ArticleRecords, 0)
	err = db.Model(&ArticleRecords{}).
		Where("article_id = ?", articleId).
		Where("misjudged = 1").
		Group("reader_id").
		Select("reader_id").
		Find(&lists).
		Error
	if err != nil {
		return err
	}
	if len(lists) == 0 {
		return nil
	}
	readerIds := make([]int64, 0)
	for _, list := range lists {
		readerIds = append(readerIds, list.ReaderId)
	}
	misjudged := make([]*ArticleRecords, 0)
	err = db.Table("article a").
		Joins("left join article_records b on a.id = b.article_id").
		Joins("left join user c on b.reader_id = c.id").
		Where("a.created_at BETWEEN ? AND ?", monthStart.Unix(), now.Unix()).
		Where("a.state = 25").
		Where("b.misjudged = 1").
		Where("b.reader_id IN (?)", readerIds).
		Where("c.state = 1").
		Where("( c.last_freeze_at = 0 OR ( c.last_freeze_at > 0 AND a.created_at > c.last_freeze_at )").
		Group("reader_id").
		Select("COUNT(*) as id", "reader_id").
		Find(&misjudged).
		Error
	if err != nil {
		return err
	}
	freezes := make([]int64, 0)
	misjudgedMap := make(map[int64]int64)
	for _, v := range misjudged {
		misjudgedMap[v.ReaderId] = v.Id
		freezes = append(freezes, v.ReaderId)
	}

	if len(misjudgedMap) > 0 {
		// 冻结审读员
		err = db.Model(&User{}).Where("id IN (?)", freezes).Updates(map[string]interface{}{
			"state":            2,
			"state_reason":     fmt.Sprintf("本月误判次数达到%d次", misjudgedTimes),
			"return_normal_at": now.Unix() + freezeDays*86400,
			"updated_at":       now.Unix(),
		}).Error
		if err != nil {
			return err
		}
	}

	return nil
}

// 自动解冻被冻结的账户
func (s *Articles) unfreeze() {
	db := s.db.DB()
	lists := make([]*User, 0)
	now := time.Now()
	db.Model(&User{}).
		Where("state = 2 AND return_normal_at < ?", now.Unix()).
		Limit(100).
		Find(&lists)
	if len(lists) == 0 {
		return
	}
	ids := make([]int64, 0)
	for _, list := range lists {
		ids = append(ids, list.Id)
	}
	db.Model(&User{}).
		Where("id IN (?) AND state = 2", ids).
		Updates(map[string]interface{}{
			"state":            1,
			"state_reason":     "",
			"return_normal_at": 0,
			"updated_at":       now.Unix(),
		})
}

// articleDeadline 稿件超时 状态自动流转到已超时状态
func (s *Articles) articleDeadline() {
	timestamp := util.Timestamp()
	db := s.db.DB()
	articles := make([]*Article, 0)
	db.Model(&Article{}).
		// Where("state IN (1,9,17)").
		Where("state = 1").
		Where("deadline_at > 0 AND deadline_at < ?", timestamp).
		Limit(100).
		Find(&articles)
	if len(articles) == 0 {
		return
	}
	ids := make([]int64, 0, len(articles))
	for _, v := range articles {
		ids = append(ids, v.Id)
	}
	db.Model(&Article{}).
		Where("id IN (?)", ids).
		Updates(map[string]any{
			"state":      33,
			"updated_at": timestamp,
		})
	return
}

type KeyValueInt32Int64 struct {
	K int32 `gorm:"column:k"`
	V int64 `gorm:"column:v"`
}

func (s *Articles) GetArticleReaderTaskListCount(ctx context.Context, req *v1.GetArticleReaderTaskListCountRequest) (*v1.GetArticleReaderTaskListCountReply, error) {
	reply := &v1.GetArticleReaderTaskListCountReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	reply.Data = &v1.GetArticleReaderTaskListCountReply_Item{}

	article := &Article{}
	db := s.db.DB()

	todayStart, todayEnd := util.TodayStartEnd()
	{
		err := db.Model(article).
			WithContext(ctx).
			Where("created_at BETWEEN ? AND ?", todayStart, todayEnd).
			Where("deleted_at = 0").
			Count(&reply.Data.TodayTaskCount).Error
		if err != nil {
			return nil, err
		}
	}
	{
		err := db.Model(article).
			WithContext(ctx).
			Where("state IN (?)", []any{9, 17, 25}).
			Where("deleted_at = 0").
			Count(&reply.Data.ReceivedTaskCount).Error
		if err != nil {
			return nil, err
		}
	}
	{
		err := db.Model(article).
			WithContext(ctx).
			Where("state = ?", 25).
			Where("deleted_at = 0").
			Count(&reply.Data.CompletedTaskCount).Error
		if err != nil {
			return nil, err
		}
	}
	return reply, nil
}

type ArticleReaderTaskListItem struct {
	// id
	Id int64 `gorm:"column:id"`
	// 标题
	Title string `gorm:"column:title"`
	// 媒体名称
	Media *string `gorm:"column:media"`
	// 平台名称
	Platform *string `gorm:"column:platform"`
	// 频道名称
	ChannelName *string `gorm:"column:channel_name"`
	// 差错类型
	Errors []string `gorm:"-"`
	// 发布时间
	PublishAt int64 `gorm:"column:publish_at"`
	// 信源id
	SourceId int64 `gorm:"column:source_id"`
}

func (s *Articles) GetArticleReaderTaskList(ctx context.Context, req *v1.GetArticleReaderTaskListRequest) (*v1.GetArticleReaderTaskListReply, error) {
	reply := &v1.GetArticleReaderTaskListReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	reply.Data = make([]*v1.GetArticleReaderTaskListReply_Item, 0)

	db := s.db.DB()
	timestamp := util.Timestamp()
	userId := req.GetUserId()

	sourcesIds := make([]int64, 0)
	// 允许的信源
	err := db.Table((&UserApproveSources{}).TableName()+" a").
		Joins("INNER JOIN "+(&ApproveSources{}).TableName()+" b ON a.approve_sources_id = b.id").
		Where("a.user_id = ?", userId).
		Select("b.id").
		Find(&sourcesIds).
		Error
	if err != nil {
		return nil, err
	}
	if len(sourcesIds) == 0 {
		return reply, nil
	}
	// 排除当前用户领过的稿件
	received := db.Model(&ArticleReader{}).Where("reader_id = ?", userId).Select("article_id AS id")
	// 稿件表
	table := db.Model(&Article{}).
		Where("deadline_at > ? AND category = 1 AND deleted_at = 0 AND state IN (1,9) AND claimed = 0 AND source_id IN (?) AND id NOT IN (?)", timestamp, sourcesIds, received)
	err = table.Count(&reply.Total).Error
	if err != nil {
		return nil, err
	}
	table.Select("id,title,publish_at,source_id")
	page := getPage(req.GetPage())
	limit := getLimit(req.GetLimit())
	offset := getOffset(page, limit)
	table.Order("deadline_at ASC").Limit(limit).Offset(offset)
	items := make([]*ArticleReaderTaskListItem, 0)
	err = table.Scan(&items).Error
	if err != nil {
		return nil, err
	}
	approveSourcesLists := make([]*ApproveSources, 0)
	approveSourcesMap := make(map[int64]*ApproveSources)
	{
		err = db.Model(&ApproveSources{}).
			Where("id IN (?)", sourcesIds).
			Find(&approveSourcesLists).Error
		if err != nil {
			return nil, err
		}
		for _, item := range approveSourcesLists {
			approveSourcesMap[item.Id] = item
		}
	}
	for _, v := range items {
		w := &v1.GetArticleReaderTaskListReply_Item{
			Id:        v.Id,
			Title:     v.Title,
			PublishAt: v.PublishAt,
		}
		tmp, ok := approveSourcesMap[v.SourceId]
		if ok && tmp != nil {
			w.Media = &tmp.Media
			w.Platform = &tmp.Platform
			w.ChannelName = &tmp.ChannelName
		}
		reply.Data = append(reply.Data, w)
	}
	// 查询稿件 巡检结果的所有错误类型
	{
		records := &ArticleRecords{}
		recordsLists := make([]*ArticleRecords, 0)
		err = db.Model(records).
			Select("article_id, error_type").
			Where("from_type = 1").
			Group("article_id, error_type").
			Find(&recordsLists).Error
		if err != nil {
			return nil, err
		}
		recordsListsMap := make(map[int64][]string)
		recordsListsMapMap := make(map[int64]map[string]*struct{})
		for _, v := range recordsLists {
			if v.ErrorType == "" {
				continue
			}
			if _, ok := recordsListsMap[v.ArticleId]; !ok {
				recordsListsMap[v.ArticleId] = make([]string, 0, 1)
				recordsListsMapMap[v.ArticleId] = make(map[string]*struct{})
			}
			if _, ok := recordsListsMapMap[v.ArticleId][v.ErrorType]; ok {
				continue
			}
			recordsListsMapMap[v.ArticleId][v.ErrorType] = nil
			recordsListsMap[v.ArticleId] = append(recordsListsMap[v.ArticleId], v.ErrorType)
		}
		for _, v := range reply.Data {
			if w, ok := recordsListsMap[v.Id]; ok {
				v.Errors = w
			} else {
				v.Errors = make([]string, 0)
			}
		}
	}
	return reply, nil
}

func (s *Articles) GetArticleReaderTaskDoing(ctx context.Context, req *v1.GetArticleReaderTaskDoingRequest) (*v1.GetArticleReaderTaskDoingReply, error) {
	reply := &v1.GetArticleReaderTaskDoingReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	reply.Data = &v1.GetArticleReaderTaskDoingReply_Data{}

	article := &ArticleReader{}
	db := s.db.DB()

	lists := make([]*ArticleReader, 0)
	err := db.Table(article.TableName()+" a").
		WithContext(ctx).
		Joins("LEFT JOIN "+(&Article{}).TableName()+" b ON a.article_id = b.id").
		Where("a.reader_id = ? AND a.state = 1", req.UserId).
		Where("a.deleted_at = 0").
		Select("a.article_id", "COALESCE(b.title,'') as note").
		Limit(1).
		Find(&lists).
		Error
	if err != nil {
		return nil, err
	}
	if len(lists) > 0 {
		reply.Data.ArticleId = lists[0].ArticleId
		reply.Data.Title = lists[0].Note
	}
	return reply, nil
}

func (s *Articles) PostArticleReaderGrabArticles(ctx context.Context, req *v1.PostArticleReaderGrabArticlesRequest) (*v1.PostArticleReaderGrabArticlesReply, error) {
	reply := &v1.PostArticleReaderGrabArticlesReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	article := &Article{}
	db := s.db.DB()
	timestamp := util.Timestamp()

	users := make([]*User, 0)
	db.Model(&User{}).Where("id = ?", req.UserId).Limit(1).Find(&users)
	if len(users) == 0 {
		reply.StatusCode = 500
		reply.Message = "账户信息不存在"
		return reply, nil
	}
	user := users[0]

	readerId := req.GetUserId()

	if user.IsVerifier != 1 {
		doing := make([]*ArticleReader, 0)
		err := db.Model(&ArticleReader{}).
			WithContext(ctx).
			Where("reader_id = ? AND state = 1", readerId).
			Where("deleted_at = 0").
			Select("id").
			Limit(1).
			Find(&doing).Error
		if err != nil {
			return nil, err
		}
		if len(doing) > 0 {
			reply.StatusCode = 500
			reply.Message = "请先处理进行中的任务"
			return reply, nil
		}
	}

	s.grabArticles.Lock()
	defer s.grabArticles.Unlock()

	lists := make([]*Article, 0)
	err := db.Model(article).
		WithContext(ctx).
		Where("id = ?", req.ArticleId).
		Where("deleted_at = 0").
		Select("id, reader_id, state, claimed, deadline_at, publish_at, verifier_id").
		Limit(1).
		Find(&lists).Error
	if err != nil {
		return nil, err
	}
	if len(lists) == 0 {
		reply.StatusCode = 500
		reply.Message = "稿件不存在"
		return reply, nil
	}
	exists := lists[0]
	if user.IsVerifier != 1 {
		if exists.DeadlineAt > 0 && timestamp > exists.DeadlineAt {
			reply.StatusCode = 500
			reply.Message = "该稿件已超时"
			return reply, nil
		}
		if exists.State != 1 && exists.State != 9 {
			reply.StatusCode = 500
			reply.Message = "该稿件无法领取"
			return reply, nil
		}
		if exists.Claimed != 0 {
			reply.StatusCode = 500
			reply.Message = "当前任务已被领取,请刷新页面重新领取其它任务"
			return reply, nil
		}
	} else {
		if exists.State == 25 {
			reply.StatusCode = 500
			reply.Message = "该稿件已结案"
			return reply, nil
		}
		if exists.VerifierId > 0 {
			if exists.VerifierId == *req.UserId {
				reply.StatusCode = 200
				reply.Message = "success"
				return reply, nil
			} else {
				reply.StatusCode = 500
				reply.Message = "该稿件已被其他人审读"
				return reply, nil
			}
		}
	}
	_ = db.Transaction(func(tx *gorm.DB) error {
		updates := map[string]interface{}{}
		if user.IsVerifier == 1 {
			updates["state"] = 9
		} else {
			if exists.State == 1 {
				updates["state"] = 9
			}
		}
		if exists.Claimed != 1 {
			updates["claimed"] = 1
		}
		if user.IsVerifier == 1 {
			updates["verifier_id"] = user.Id
			updates["verifier_at"] = timestamp
		}
		if len(updates) > 0 {
			updates["updated_at"] = timestamp
			tmp := db.Model(article).Where("id = ?", req.ArticleId).Updates(updates)
			if err = tmp.Error; err != nil {
				return err
			}
			if tmp.RowsAffected == 0 {
				if user.IsVerifier == 1 {
					reply.StatusCode = 500
					reply.Message = "当前稿件正在被其他人复核"
				} else {
					reply.StatusCode = 500
					reply.Message = "当前任务已被领取,请刷新页面重新领取其它任务"
				}
				return nil
			}
		}
		create := &ArticleReader{
			ArticleId:  req.ArticleId,
			ReaderId:   readerId,
			ReaderAt:   timestamp,
			DeadlineAt: timestamp + req.GetReaderDuration(),
			State:      1,
		}
		if user.IsVerifier == 1 {
			create.DeadlineAt = 0
		}
		create.CreatedAt = timestamp
		create.UpdatedAt = timestamp
		tmp := db.Model(create).Create(&create)
		if err = tmp.Error; err != nil {
			return err
		}
		if tmp.RowsAffected == 0 {
			reply.StatusCode = 500
			reply.Message = "领取失败"
			return errors.New("领取失败")
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return reply, nil
}

type articleReaderTaskDetails struct {
	Id                   int64   `gorm:"auto_increment;primary_key;not null;column:id;comment:id"`
	Title                string  `gorm:"column:title;not null;default:'';size:255;comment:标题"`
	Content              *string `gorm:"column:content;type:longtext;comment:内容"`
	Keyword              string  `gorm:"column:keyword;not null;default:'';size:255;comment:关键词"`
	Category             int32   `gorm:"column:category;not null;default:0;comment:任务分类 1:推送任务 2:人工提交任务"`
	ReaderId             int64   `gorm:"column:reader_id;not null;default:0;comment:审读员id,锁定审读员"`
	ReaderAt             int64   `gorm:"column:reader_at;not null;default:0;comment:审读员领取任务时间"`
	State                int32   `gorm:"column:state;not null;default:0;comment:审读状态 0:未知 1:未领取 9:审读中 17:待复核 25:已结案 33:已超时"`
	PublishAt            int64   `gorm:"column:publish_at;not null;default:0;comment:发布时间;index"`
	DeadlineAt           int64   `gorm:"column:deadline_at;not null;default:0;comment:截止时间;index "`
	Editor               string  `gorm:"column:editor;not null;default:'';size:32;comment:稿件编辑"`
	EditorCharge         string  `gorm:"column:editor_charge;not null;default:'';size:32;comment:稿件责编"`
	EditorMain           string  `gorm:"column:editor_main;not null;default:'';size:32;comment:稿件主编"`
	PlatformEditor       string  `gorm:"column:platform_editor;not null;default:'';size:32;comment:稿件编辑(当前平台)"`
	PlatformEditorCharge string  `gorm:"column:platform_editor_charge;not null;default:'';size:32;comment:稿件责编(当前平台)"`
	PlatformEditorMain   string  `gorm:"column:platform_editor_main;not null;default:'';size:32;comment:稿件主编(当前平台)"`
	SourceUrl            *string `gorm:"column:source_url;type:text;comment:源文链接"`
	Briefing             *string `gorm:"column:briefing;type:text;comment:稿件简报"`
	CreatedAt            int64   `gorm:"column:created_at;not null;default:0;comment:创建时间"`
	MediaIdManual        int64   `gorm:"column:media_id_manual;not null;default:0;comment:媒体id-人工提交"`
	ProblemAreaManual    string  `gorm:"column:problem_area_manual;not null;default:'';comment:问题区域-人工提交"`
	VerifierId           int64   `gorm:"column:verifier_id;not null;default:0;comment:复核员id"`

	MediaId     *int64  `gorm:"column:media_id;not null;default:0;comment:媒体id"`
	Media       *string `gorm:"column:media;not null;default:'';comment:媒体名称"`
	PlatformId  *int64  `gorm:"column:platform_id;not null;default:0;comment:平台id"`
	Platform    *string `gorm:"column:platform;not null;default:'';comment:平台名称"`
	ChannelName *string `gorm:"column:channel_name;not null;default:'';size:255;comment:频道名称"`
}

func (s *Articles) GetArticleReaderTaskDetails(ctx context.Context, req *v1.GetArticleReaderTaskDetailsRequest) (*v1.GetArticleReaderTaskDetailsReply, error) {
	reply := &v1.GetArticleReaderTaskDetailsReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	reply.Data = &v1.GetArticleReaderTaskDetailsReply_Data{}
	article := &Article{}
	db := s.db.DB()
	lists := make([]*articleReaderTaskDetails, 0)
	err := db.Table(article.TableName()).
		Joins("a LEFT JOIN approve_sources b ON a.source_id = b.id").
		WithContext(ctx).
		Where("a.id = ?", req.ArticleId).
		Where("a.deleted_at = 0").
		Select("a.id, a.title, a.content, a.keyword, a.category, a.reader_id, a.reader_at, a.publish_at, a.deadline_at, a.editor, a.editor_charge, a.editor_main, a.platform_editor, a.platform_editor_charge, a.platform_editor_main, a.source_url, a.briefing, a.created_at, a.state, a.media_id_manual, a.problem_area_manual, b.media_id, b.media, b.platform_id, b.platform, b.channel_name").
		Limit(1).
		Find(&lists).Error
	if err != nil {
		return nil, err
	}
	if len(lists) == 0 {
		reply.StatusCode = 500
		reply.Message = "稿件不存在"
		return reply, nil
	}
	v := lists[0]
	{
		readers := make([]*ArticleReader, 0)
		db.Model(&ArticleReader{}).Where("article_id = ? AND reader_id = ? AND state = 1", req.ArticleId, req.GetUserId()).Find(&readers)
		if len(readers) > 0 {
			v.ReaderAt = readers[0].ReaderAt
		}
	}
	reply.Data = &v1.GetArticleReaderTaskDetailsReply_Data{
		Id:                   v.Id,
		Title:                v.Title,
		Content:              v.Content,
		Keyword:              v.Keyword,
		Category:             v.Category,
		ReaderId:             v.ReaderId,
		ReaderAt:             v.ReaderAt,
		PublishAt:            v.PublishAt,
		DeadlineAt:           v.DeadlineAt,
		Editor:               v.Editor,
		EditorCharge:         v.EditorCharge,
		EditorMain:           v.EditorMain,
		PlatformEditor:       v.PlatformEditor,
		PlatformEditorCharge: v.PlatformEditorCharge,
		PlatformEditorMain:   v.PlatformEditorMain,
		SourceUrl:            v.SourceUrl,
		Briefing:             v.Briefing,
		CreatedAt:            v.CreatedAt,
		State:                v.State,
		MediaId:              v.MediaId,
		Media:                v.Media,
		PlatformId:           v.PlatformId,
		Platform:             v.Platform,
		ChannelName:          v.ChannelName,
		MediaIdManual:        v.MediaIdManual,
		ProblemAreaManual:    v.ProblemAreaManual,
	}
	articleRecords := &ArticleRecords{}
	articleRecordsLists := make([]*ArticleRecords, 0)
	err = db.Table(articleRecords.TableName()+" a").
		Joins("LEFT JOIN "+(&User{}).TableName()+" b ON a.reader_id = b.id").
		Where("a.article_id = ? AND a.deleted_at = 0", req.ArticleId).
		Select("a.`id`, a.`article_id`, a.`from_type`, a.`reader_id`, a.`reader_at`, a.`reader_check`, a.`reader_draft`, a.`verifier_id`, a.`error_category`, a.`error_urls`, a.`error_location`, a.`error_type`, a.`error_type_int`, a.`error_subtype`, a.`error_reason`, a.`error_message`, a.`error_modify_advice`, a.`error_level`, a.`error_content`, a.`error_video_screenshot`, a.`reader_score`, a.`verifier_score_at`, a.`misjudged`, a.`confidence_level`, a.`conclusion_type`, a.`conclusion`, a.`created_at`, a.`updated_at`, a.`deleted_at`", "COALESCE(b.real_name,'') as note").
		Find(&articleRecordsLists).
		Error
	if err != nil {
		return nil, err
	}
	reply.Data.Records = make([]*v1.GetArticleReaderTaskDetailsReply_Data_Records, 0, len(articleRecordsLists))
	for _, w := range articleRecordsLists {
		reply.Data.Records = append(reply.Data.Records, &v1.GetArticleReaderTaskDetailsReply_Data_Records{
			Id:                   w.Id,
			ArticleId:            w.ArticleId,
			FromType:             w.FromType,
			ReaderId:             w.ReaderId,
			ReaderAt:             w.ReaderAt,
			ReaderDraft:          w.ReaderDraft,
			VerifierId:           w.VerifierId,
			ErrorCategory:        w.ErrorCategory,
			ErrorUrls:            w.ErrorUrls,
			ErrorLocation:        w.ErrorLocation,
			ErrorType:            w.ErrorType,
			ErrorTypeInt:         w.ErrorTypeInt,
			ErrorSubtype:         w.ErrorSubtype,
			ErrorReason:          w.ErrorReason,
			ErrorMessage:         w.ErrorMessage,
			ErrorModifyAdvice:    w.ErrorModifyAdvice,
			ErrorLevel:           w.ErrorLevel,
			ErrorContent:         w.ErrorContent,
			ErrorVideoScreenshot: w.ErrorVideoScreenshot,
			ReaderScore:          w.ReaderScore,
			VerifierScoreAt:      w.VerifierScoreAt,
			Misjudged:            w.Misjudged,
			Note:                 w.Note,
			ConfidenceLevel:      w.ConfidenceLevel,
			ConclusionType:       w.ConclusionType,
			Conclusion:           w.Conclusion,
			CreatedAt:            w.CreatedAt,
			ReaderCheck:          int32(w.ReaderCheck),
			ReaderName:           *w.Note,
		})
	}
	return reply, nil
}

func (s *Articles) PostArticleEditEditors(ctx context.Context, req *v1.PostArticleEditEditorsRequest) (*v1.PostArticleEditEditorsReply, error) {
	reply := &v1.PostArticleEditEditorsReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	article := &Article{}
	db := s.db.DB()
	timestamp := util.Timestamp()
	doing := make([]*Article, 0)
	err := db.Model(article).
		WithContext(ctx).
		Where("id = ?", req.ArticleId).
		Where("deleted_at = 0").
		Select("id").
		Limit(1).
		Find(&doing).Error
	if err != nil {
		return nil, err
	}
	if len(doing) > 0 {
		reply.StatusCode = 500
		reply.Message = "稿件信息不存在"
		return reply, nil
	}
	updates := make(map[string]any, 8)
	if req.PlatformEditor != nil {
		updates["platform_editor"] = req.GetPlatformEditor()
	}
	if req.PlatformEditorCharge != nil {
		updates["platform_editor_charge"] = req.GetPlatformEditorCharge()
	}
	if req.PlatformEditorMain != nil {
		updates["platform_editor_main"] = req.GetPlatformEditorMain()
	}
	if len(updates) == 0 {
		return reply, nil
	}
	updates["updated_at"] = timestamp
	err = db.Model(article).
		WithContext(ctx).
		Where("id = ?", req.ArticleId).
		Updates(updates).Error
	if err != nil {
		return nil, err
	}
	return reply, nil
}

// afterReaderCommit 审读员提交之后的
func (s *Articles) afterReaderCommit(articleId int64) error {
	db := s.db.DB()
	article := &Article{}
	articles := make([]*Article, 0)
	db.Model(article).Where("id = ? AND deleted_at = 0", articleId).Limit(1).Find(&articles)
	if len(articles) == 0 {
		return nil
	}
	timestamp := util.Timestamp()
	firstArticle := articles[0]
	if firstArticle.DeadlineAt > 0 && firstArticle.DeadlineAt < timestamp {
		if firstArticle.State == 9 {
			// 审读超时
			return db.Model(article).Where("id = ?", articleId).Updates(map[string]interface{}{
				"state":      33,
				"updated_at": timestamp,
			}).Error
		}
	}
	if firstArticle.State != 9 {
		// 不是审读中的状态,不予处理
		return nil
	}
	// 下面只处理推送任务
	if firstArticle.Category != 1 {
		return nil
	}
	// 多少人判定无误
	readerNoErrorNum, err := s.basic.getCfgInt("B05")
	if err != nil {
		return err
	}
	// 多少人判定有误
	readerErrorNum, err := s.basic.getCfgInt("B06")
	if err != nil {
		return err
	}
	// 需多少人再次判定
	readerErrorAgainNum, err := s.basic.getCfgInt("B07")
	if err != nil {
		return err
	}

	readReaders := make([]*ArticleReader, 0)
	err = db.Model(&ArticleReader{}).
		Where("article_id = ?", articleId).
		Where("state = 2").
		Select("reader_id").
		Group("reader_id").
		Find(&readReaders).
		Error
	if err != nil {
		return err
	}
	// 所有已审读该稿件的审读员
	readReadersMap := make(map[int64]*struct{})
	for _, v := range readReaders {
		readReadersMap[v.ReaderId] = nil
	}

	articleRecords := make([]*ArticleRecords, 0) // 该稿件的所已提交的差错
	err = db.Model(&ArticleRecords{}).
		Where("article_id = ? AND from_type IN ( 1, 2 ) AND reader_id > 0 AND reader_draft = 0 AND deleted_at = 0", articleId).
		Select("reader_id", "reader_check", "from_type").
		Find(&articleRecords).Error
	if err != nil {
		return err
	}
	readerMachineNoError := make(map[int64]*struct{}) // 审读员-机审无误
	readerMachineError := make(map[int64]*struct{})   // 审读员-机审有误
	readerCommitError := make(map[int64]*struct{})    // 审读员-人审有误
	for _, v := range articleRecords {
		if v.FromType == 1 {
			// 机审
			if v.ReaderCheck == 0 || v.ReaderCheck == 2 {
				readerMachineNoError[v.ReaderId] = nil
			}
			if v.ReaderCheck == 1 {
				readerMachineError[v.ReaderId] = nil
			}
		}
		if v.FromType == 2 {
			// 人审
			readerCommitError[v.ReaderId] = nil
		}
	}
	/* 至少N个审读员确认无误 */
	if len(readerMachineError) == 0 && len(readerCommitError) == 0 && int64(len(readReadersMap)) >= readerNoErrorNum {
		// 至少有N位审读员判定无误,且没有审读员提交任何差错 这时自动结案
		_ = db.Transaction(func(tx *gorm.DB) error {
			err = tx.Model(&Article{}).Where("id = ? AND state = ?", articleId, firstArticle.State).Updates(map[string]any{
				"state":      25,
				"claimed":    2,
				"updated_at": timestamp,
			}).Error
			if err != nil {
				return err
			}
			err = tx.Model(&ArticleReader{}).Where("article_id = ? AND state = 2", articleId).Updates(map[string]any{
				"state":      6,
				"updated_at": timestamp,
			}).Error
			if err != nil {
				return err
			}
			return nil
		})
		return err
	}
	// 条件不满足,不予处理
	readerError := make(map[int64]*struct{})
	for id := range readerCommitError {
		readerError[id] = nil
	}
	for id := range readerMachineError {
		readerError[id] = nil
	}
	if num := len(readerError); num > 0 {
		// 存在审读员判定差错有误的情况
		if int64(num) >= readerErrorNum /* 判定有错的审读员人数达到 readerErrorNum 人 */ &&
			int64(len(readReadersMap)) >= readerErrorNum+readerErrorAgainNum /* 参数判定的审读员人数达到 readerErrorNum+readerErrorAgainNum 人*/ {
			// 到待复核状态
			_ = db.Transaction(func(tx *gorm.DB) error {
				tmp := tx.Model(&Article{}).Where("id = ? AND state = ?", articleId, firstArticle.State).Updates(map[string]any{
					"state":      17,
					"claimed":    2,
					"updated_at": timestamp,
				})
				err = tmp.Error
				if err != nil {
					return err
				}
				if tmp.RowsAffected == 0 {
					err = errors.New("稿件状态更新失败")
					return err
				}
				err = tx.Model(&ArticleReader{}).Where("article_id = ? AND state = 2", articleId).Updates(map[string]any{
					"state":      4,
					"updated_at": timestamp,
				}).Error
				if err != nil {
					return err
				}
				return nil
			})
			return err
		}
	}
	// 不做处理
	return nil
}

// AfterReaderCommit 审读员提交之后判定是否满足某些条件(自动转待复核,自动结案)
func (s *Articles) AfterReaderCommit(articleId int64) error {
	err := s.afterReaderCommit(articleId)
	if err != nil {
		s.logger.Log(log.LevelWarn, "error", err.Error(), "title", "自动结案")
	}
	return err
}

type postArticleReaderCommitOther struct {
	// 稿件编辑(当前平台)
	PlatformEditor *string `protobuf:"bytes,4,opt,name=platformEditor,proto3,oneof" json:"platformEditor,omitempty"`
	// 稿件责编(当前平台)
	PlatformEditorCharge *string `protobuf:"bytes,5,opt,name=platformEditorCharge,proto3,oneof" json:"platformEditorCharge,omitempty"`
	// 稿件主编(当前平台)
	PlatformEditorMain *string `protobuf:"bytes,6,opt,name=platformEditorMain,proto3,oneof" json:"platformEditorMain,omitempty"`
}

func (s *Articles) postArticleReaderCommit(
	ctx context.Context,
	userId int64,
	articleId int64,
	lists []*v1.PostArticleReaderCommitArticleRecord,
	isDraft bool,
	other *postArticleReaderCommitOther,
) (err error) {
	length := len(lists)
	if length == 0 {
		return nil
	}
	db := s.db.DB()
	timestamp := util.Timestamp()

	users := make([]*User, 0)
	db.Model(&User{}).Where("id = ?", userId).Limit(1).Find(&users)
	if len(users) == 0 {
		return errors.New("账户不存在")
	}
	user := users[0]
	if user.State != 1 {
		return errors.New("账户状态异常")
	}

	article := &Article{}
	articleList := make([]*Article, 0)
	if err = db.Model(article).
		WithContext(ctx).
		Where("id = ? AND deleted_at = 0", articleId).
		Select("id, reader_id, state, deadline_at").
		Limit(1).
		Find(&articleList).Error; err != nil {
		return err
	}
	if len(articleList) == 0 {
		return errors.New("稿件信息不存在")
	}
	first := articleList[0]
	if user.IsVerifier != 1 {
		if first.State != 9 {
			return errors.New("稿件状态不匹配")
		}
		if first.DeadlineAt > 0 && first.DeadlineAt < timestamp {
			return errors.New("稿件已超时")
		}
	}
	readers := make([]*ArticleReader, 0)
	if err = db.Model(&ArticleReader{}).
		Where("article_id = ? AND state = 1 AND deleted_at = 0", articleId).
		Limit(1).
		Find(&readers).
		Error; err != nil {
		return err
	}
	if len(readers) == 0 {
		return errors.New("非法操作")
	}
	articleReader := readers[0]
	if articleReader.ReaderId != userId {
		return errors.New("非法操作")
	}
	if user.IsVerifier != 1 {
		if timestamp > readers[0].DeadlineAt {
			return errors.New("审读超时")
		}
	}

	articleRecords := &ArticleRecords{}
	create := make([]*ArticleRecords, 0, length)
	modify := make([]*ArticleRecords, 0, length)
	modifyIds := make([]int64, 0, length)
	modifyMap := make(map[int64]*v1.PostArticleReaderCommitArticleRecord, length)
	for _, v := range lists {
		if v == nil {
			continue
		}
		if isDraft {
			draft := int32(1)
			v.ReaderDraft = &draft
		}
		if v.Id != nil && *v.Id > 0 {
			id := v.GetId()
			modifyIds = append(modifyIds, id)
			modifyMap[id] = v
		} else {
			tmp := &ArticleRecords{
				ErrorCategory:        v.GetErrorCategory(),
				ErrorUrls:            v.ErrorUrls,
				ErrorLocation:        v.ErrorLocation,
				ErrorType:            v.GetErrorType(),
				ErrorTypeInt:         v.GetErrorTypeInt(),
				ErrorSubtype:         v.GetErrorSubtype(),
				ErrorReason:          v.GetErrorReason(),
				ErrorMessage:         v.GetErrorMessage(),
				ErrorModifyAdvice:    v.ErrorModifyAdvice,
				ErrorLevel:           v.GetErrorLevel(),
				ErrorContent:         v.ErrorContent,
				ErrorVideoScreenshot: v.ErrorVideoScreenshot,
				ReaderScore:          0,
				VerifierScoreAt:      0,
				Misjudged:            0,
				Note:                 v.Note,
				ConfidenceLevel:      v.GetConfidenceLevel(),
				ConclusionType:       v.GetConclusionType(),
				Conclusion:           v.GetConclusion(),
			}
			tmp.ArticleId = articleId
			tmp.FromType = 2
			tmp.ReaderId = userId
			tmp.ReaderAt = timestamp
			tmp.ReaderCheck = 1
			tmp.ReaderDraft = v.GetReaderDraft()
			tmp.CreatedAt = timestamp
			tmp.UpdatedAt = timestamp
			create = append(create, tmp)
		}
	}
	updates := make([]map[string]any, 0, length)
	if len(modifyIds) > 0 {
		err = db.Model(articleRecords).
			WithContext(ctx).
			Where("id IN (?) AND deleted_at = 0", modifyIds).
			Find(&modify).Error
		if err != nil {
			return err
		}
		for _, v := range modify {
			w, ok := modifyMap[v.Id]
			if !ok {
				continue
			}
			if w.FromType == nil {
				continue
			}
			update := make(map[string]any, 8)
			// 机审更新
			if *w.FromType == 1 {
				if w.ReaderCheck != nil && *w.ReaderCheck != int32(v.ReaderCheck) {
					update["reader_check"] = w.ReaderCheck
					if *w.ReaderCheck == 1 {
						update["reader_id"] = userId
						update["reader_at"] = timestamp
					} else {
						update["reader_id"] = 0
						update["reader_at"] = 0
					}
				}
				if w.ReaderDraft != nil && *w.ReaderDraft != v.ReaderDraft {
					update["reader_draft"] = w.ReaderDraft
				}
				if len(update) > 0 {
					update["id"] = v.Id
					update["updated_at"] = timestamp
					updates = append(updates, update)
				}
			}
			// 人审更新
			if *w.FromType == 2 {
				if w.ErrorUrls != nil {
					update["error_urls"] = w.GetErrorUrls()
				}
				if w.ErrorLocation != nil {
					update["error_location"] = w.GetErrorLocation()
				}
				if w.ErrorType != nil {
					update["error_type"] = w.GetErrorType()
				}
				if w.ErrorReason != nil {
					update["error_reason"] = w.GetErrorReason()
				}
				if w.ErrorMessage != nil {
					update["error_message"] = w.GetErrorMessage()
				}
				if w.ErrorModifyAdvice != nil {
					update["error_modify_advice"] = w.GetErrorModifyAdvice()
				}
				if len(update) > 0 {
					update["id"] = v.Id
					update["updated_at"] = timestamp
					updates = append(updates, update)
				}
			}
		}
	}
	createLength := len(create)
	modifyLength := len(updates)
	articleUpdate := map[string]any{}
	if other.PlatformEditor != nil {
		articleUpdate["platform_editor"] = *other.PlatformEditor
	}
	if other.PlatformEditorCharge != nil {
		articleUpdate["platform_editor_charge"] = *other.PlatformEditorCharge
	}
	if other.PlatformEditorMain != nil {
		articleUpdate["platform_editor_main"] = *other.PlatformEditorMain
	}
	if len(articleUpdate) > 0 {
		articleUpdate["updated_at"] = timestamp
	}
	err = db.Transaction(func(tx *gorm.DB) error {
		if createLength > 0 {
			if err = tx.Model(articleRecords).Create(&create).Error; err != nil {
				return err
			}
		}
		if modifyLength > 0 {
			for _, v := range updates {
				mod := tx.Model(articleRecords).Where("id = ?", v["id"])
				delete(v, "id")
				if err = mod.Updates(v).Error; err != nil {
					return err
				}
			}
		}
		if isDraft && len(articleUpdate) > 0 {
			if err = tx.Model(article).
				Where("id = ?", articleId).
				Updates(articleUpdate).Error; err != nil {
				return err
			}
		}
		if !isDraft {
			// 先更新当前审读员的提交状态
			articleReaderUpdate := map[string]interface{}{
				"state":      2,
				"commit_at":  timestamp,
				"updated_at": timestamp,
			}
			if user.IsVerifier == 1 {
				articleReaderUpdate["state"] = 6
			}
			err = tx.Model(&ArticleReader{}).
				Where("id = ?", articleReader.Id).
				Updates(articleReaderUpdate).
				Error
			if err != nil {
				return err
			}

			articleUpdate["claimed"] = 0
			articleUpdate["updated_at"] = timestamp
			if user.IsVerifier == 1 {
				articleUpdate["state"] = 25
			}
			err = tx.Model(&Article{}).
				Where("id = ?", articleId).
				Updates(articleUpdate).Error
			if err != nil {
				return err
			}
			articleRecordsUpdate := map[string]any{
				"reader_draft": 0,
				"updated_at":   timestamp,
			}
			err = tx.Model(&ArticleRecords{}).
				Where("article_id = ?", articleId).
				Where("reader_id = ?", userId).
				Where("reader_draft = 1").
				Where("deleted_at = 0").
				Updates(articleRecordsUpdate).Error
			if err != nil {
				return err
			}
		}
		return nil
	})
	if err != nil {
		return err
	}
	// 审读员提交之后的后续处理
	if !isDraft && user.IsVerifier != 1 {
		go func() {
			_ = s.AfterReaderCommit(articleId)
		}()
	}
	return nil
}

func (s *Articles) PostArticleReaderCommitDraft(ctx context.Context, req *v1.PostArticleReaderCommitDraftRequest) (*v1.PostArticleReaderCommitDraftReply, error) {
	reply := &v1.PostArticleReaderCommitDraftReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	if req.ArticleId <= 0 {
		reply.StatusCode = 500
		reply.Message = "错误参数值article_id"
		return reply, nil
	}
	if req.GetUserId() <= 0 {
		reply.StatusCode = 500
		reply.Message = "非法请求"
		return reply, nil
	}
	other := &postArticleReaderCommitOther{
		PlatformEditor:       req.PlatformEditor,
		PlatformEditorCharge: req.PlatformEditorCharge,
		PlatformEditorMain:   req.PlatformEditorMain,
	}
	err := s.postArticleReaderCommit(ctx, req.GetUserId(), req.ArticleId, req.Lists, true, other)
	if err != nil {
		reply.StatusCode = 500
		reply.Message = err.Error()
		return reply, nil
	}
	return reply, nil
}

func (s *Articles) PostArticleReaderCommit(ctx context.Context, req *v1.PostArticleReaderCommitRequest) (*v1.PostArticleReaderCommitReply, error) {
	reply := &v1.PostArticleReaderCommitReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	if req.ArticleId <= 0 {
		reply.StatusCode = 500
		reply.Message = "错误参数值article_id"
		return reply, nil
	}
	if req.GetUserId() <= 0 {
		reply.StatusCode = 500
		reply.Message = "非法请求"
		return reply, nil
	}
	other := &postArticleReaderCommitOther{
		PlatformEditor:       req.PlatformEditor,
		PlatformEditorCharge: req.PlatformEditorCharge,
		PlatformEditorMain:   req.PlatformEditorMain,
	}
	err := s.postArticleReaderCommit(ctx, req.GetUserId(), req.ArticleId, req.Lists, false, other)
	if err != nil {
		reply.StatusCode = 500
		reply.Message = "操作失败"
		return reply, nil
	}
	return reply, nil
}

func (s *Articles) PostArticleReaderManualCommitArticle(ctx context.Context, req *v1.PostArticleReaderManualCommitArticleRequest) (*v1.PostArticleReaderManualCommitArticleReply, error) {
	reply := &v1.PostArticleReaderManualCommitArticleReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	if req.GetUserId() <= 0 {
		reply.StatusCode = 500
		reply.Message = "非法操作"
		return reply, nil
	}

	db := s.db.DB()
	timestamp := util.Timestamp()
	article := &Article{}
	if req.ArticleId != nil && req.GetArticleId() > 0 {
		// 更新
		articleList := make([]*Article, 0)
		if err := db.Model(article).
			WithContext(ctx).
			Where("id = ? AND reader_id = ? AND deleted_at = 0", req.GetArticleId(), req.GetUserId()).
			Limit(1).
			Find(&articleList).Error; err != nil {
			return nil, err
		}
		if len(articleList) == 0 {
			reply.StatusCode = 500
			reply.Message = "稿件信息不存在"
			return reply, nil
		}
		first := articleList[0]
		updates := make(map[string]any, 8)
		updates["media_id_manual"] = req.GetMediaId()
		updates["problem_area_manual"] = req.GetProblemArea()
		updates["title"] = req.GetTitle()
		updates["publish_at"] = req.GetPublishAt()
		updates["source_url"] = req.GetSourceUrl()
		if req.Editor != nil {
			updates["editor"] = req.GetEditor()
		}
		updates["updated_at"] = timestamp
		err := db.Model(article).Where("id = ?", first.Id).Updates(updates).Error
		if err != nil {
			return nil, err
		}
		return reply, nil
	}
	create := &Article{
		Category:  2,
		ReaderId:  req.GetUserId(),
		ReaderAt:  timestamp,
		Title:     req.GetTitle(),
		PublishAt: req.GetPublishAt(),
		Editor:    req.GetEditor(),
		SourceUrl: &req.SourceUrl,
		State:     9,
	}
	create.MediaIdManual = req.GetMediaId()
	create.ProblemAreaManual = req.GetProblemArea()
	create.CreatedAt = timestamp
	create.UpdatedAt = timestamp
	var err error

	articleReader := &ArticleReader{
		ReaderId:   req.GetUserId(),
		ReaderAt:   timestamp,
		DeadlineAt: 0,
		State:      2,
	}
	articleReader.CreatedAt = timestamp
	articleReader.UpdatedAt = timestamp
	_ = db.Transaction(func(tx *gorm.DB) error {
		err = tx.Model(article).Create(create).Error
		if err != nil {
			return err
		}
		articleReader.ArticleId = create.Id
		err = tx.Model(articleReader).Create(articleReader).Error
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	reply.Data = &v1.PostArticleReaderManualCommitArticleReply_Data{
		ArticleId: &create.Id,
	}
	return reply, nil
}

func (s *Articles) PostArticleReaderManualCommitArticleRecord(ctx context.Context, req *v1.PostArticleReaderManualCommitArticleRecordRequest) (*v1.PostArticleReaderManualCommitArticleRecordReply, error) {
	reply := &v1.PostArticleReaderManualCommitArticleRecordReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	if req.ArticleId <= 0 {
		reply.StatusCode = 500
		reply.Message = "错误参数值article_id"
		return reply, nil
	}
	if req.GetUserId() <= 0 {
		reply.StatusCode = 500
		reply.Message = "非法请求"
		return reply, nil
	}
	db := s.db.DB()
	timestamp := util.Timestamp()
	article := &Article{}
	articleRecords := &ArticleRecords{}
	exists := make([]*Article, 0)
	if err := db.Model(article).
		Where("id = ? AND reader_id = ? AND deleted_at = 0", req.GetArticleId(), req.GetUserId()).
		Select("id, state").
		Find(&exists).Error; err != nil {
		return nil, err
	}
	if len(exists) == 0 {
		reply.StatusCode = 500
		reply.Message = "稿件不存在"
		return reply, nil
	}
	if exists[0].State != 9 {
		reply.StatusCode = 500
		reply.Message = "当前不可操作稿件差错"
		return reply, nil
	}
	if req.Id != nil && req.GetId() > 0 {
		// 更新
		articleRecordsList := make([]*ArticleRecords, 0)
		if err := db.Model(articleRecords).
			WithContext(ctx).
			Where("id = ? AND reader_id = ? AND deleted_at = 0", req.GetId(), req.GetUserId()).
			Limit(1).
			Find(&articleRecordsList).Error; err != nil {
			return nil, err
		}
		if len(articleRecordsList) == 0 {
			reply.StatusCode = 500
			reply.Message = "差错信息不存在"
			return reply, nil
		}
		first := articleRecordsList[0]
		updates := make(map[string]any, 8)
		updates["error_urls"] = req.GetErrorUrls()
		updates["error_location"] = req.GetErrorLocation()
		updates["error_type"] = req.GetErrorType()
		updates["error_reason"] = req.GetErrorReason()
		updates["error_message"] = req.GetErrorMessage()
		updates["error_modify_advice"] = req.GetErrorModifyAdvice()
		updates["updated_at"] = timestamp
		err := db.Model(articleRecords).Where("id = ?", first.Id).Updates(updates).Error
		if err != nil {
			return nil, err
		}
		return reply, nil
	}
	create := &ArticleRecords{
		FromType:          3,
		ReaderId:          req.GetUserId(),
		ReaderAt:          timestamp,
		ReaderCheck:       1,
		ErrorCategory:     7,
		ErrorUrls:         &req.ErrorUrls,
		ErrorLocation:     &req.ErrorLocation,
		ErrorType:         req.GetErrorType(),
		ErrorReason:       req.GetErrorReason(),
		ErrorMessage:      req.GetErrorMessage(),
		ErrorModifyAdvice: &req.ErrorModifyAdvice,
	}
	create.ArticleId = exists[0].Id
	create.CreatedAt = timestamp
	create.UpdatedAt = timestamp
	if err := db.Model(articleRecords).Create(create).Error; err != nil {
		return nil, err
	}
	reply.Data = &v1.PostArticleReaderManualCommitArticleRecordReply_Data{
		ArticleRecordsId: &create.Id,
	}
	return reply, nil
}

type articleReaderArticleListsItem struct {
	// id
	Id int64 `gorm:"column:id"`
	// 标题
	Title string `gorm:"column:title"`
	// 媒体名称
	Media *string `gorm:"column:media"`
	// 平台名称
	Platform *string `gorm:"column:platform"`
	// 频道名称
	ChannelName *string `gorm:"column:channel_name"`
	// 发布时间
	PublishAt int64 `gorm:"column:publish_at"`
	// 领取时间
	ReceiveAt int64 `gorm:"column:receive_at"`
	// 提交时间
	CommitAt int64 `gorm:"column:commit_at"`
	// 机审确认
	MachineCount int64 `gorm:"column:machine_count"`
	// 提交差错
	CommitRecordsCount int64 `gorm:"column:commit_records_count"`
	// 合集得分
	SumScores int64 `gorm:"column:sum_scores"`
	// 复核员id
	VerifierId int64 `gorm:"column:verifier_id"`
	// 复核员真实姓名
	VerifierRealName string `gorm:"column:verifier_real_name"`
	// 审读状态 0:未知 1:未领取 9:审读中 17:待复核 25:已结案 33:已超时
	State             int32  `gorm:"column:state"`
	MediaIdManual     int64  `gorm:"column:media_id_manual;not null;default:0;comment:媒体id-人工提交"`
	ProblemAreaManual string `gorm:"column:problem_area_manual;not null;default:'';comment:问题区域-人工提交"`
	CreatedAt         int64  `gorm:"column:created_at;not null;default:0;comment:创建时间"`
}

func (s *Articles) GetArticleReaderArticleLists(ctx context.Context, req *v1.GetArticleReaderArticleListsRequest) (*v1.GetArticleReaderArticleListsReply, error) {
	reply := &v1.GetArticleReaderArticleListsReply{}
	reply.StatusCode = 200
	reply.Message = "success"

	readerId := req.GetUserId()
	db := s.db.DB()
	article := &Article{}
	articleReader := &ArticleReader{}
	approveSources := &ApproveSources{}
	lists := make([]*articleReaderArticleListsItem, 0)

	subquery := db.Table(articleReader.TableName() + " a").
		Joins("INNER JOIN " + article.TableName() + " b ON a.article_id = b.id")
	subquery.Where("a.reader_id = ?", readerId)

	if req.State != nil {
		subquery.Where("a.state = ?", *req.State)
	}
	if req.CreatedAtStart != nil && req.CreatedAtEnd != nil && *req.CreatedAtEnd > *req.CreatedAtStart && *req.CreatedAtStart > 0 {
		subquery.Where("b.created_at BETWEEN ? AND ?", *req.CreatedAtStart, *req.CreatedAtEnd)
	}
	if req.GetKeyword() != "" {
		subquery.Where("b.title LIKE ?", fmt.Sprintf("%%%s%%", req.GetKeyword()))
	}
	subquery.Where("b.deleted_at = 0")
	subquery.Select(
		"a.reader_id",
		"a.reader_at as receive_at",
		"a.state",
		"a.commit_at",
		"b.id",
		"b.source_id",
		"b.title",
		"b.publish_at",
		"b.verifier_id",
		"b.media_id_manual",
		"b.problem_area_manual",
		"b.created_at",
		"b.category",
	)
	get := db.Table("(?) a", subquery).
		Joins("LEFT JOIN " + approveSources.TableName() + " b ON a.source_id = b.id")
	if req.MediaId != nil {
		get.Where("b.media_id = ?", req.GetMediaId())
	}
	if req.PlatformId != nil {
		get.Where("b.platform_id = ?", req.GetPlatformId())
	}
	if req.ChannelName != nil {
		get.Where("b.channel_name = ?", req.GetChannelName())
	}

	ikv := make([]*KeyValueInt32Int64, 0)
	db.Table("(?) a", get.Select("category")).
		Select("category as k", "count(*) as v").
		Group("category").
		Scan(&ikv)
	reply.Categories = make(map[int32]int64)
	for _, v := range ikv {
		reply.Categories[v.K] = v.V
	}

	subquery.Where("b.category = ?", req.Category)
	err := get.Count(&reply.Total).Error
	if err != nil {
		return nil, err
	}

	reply.Data = make([]*v1.GetArticleReaderArticleListsReply_Item, 0)
	if reply.Total == 0 {
		return reply, nil
	}
	page := getPage(req.GetPage())
	limit := getLimit(req.GetLimit())
	offset := getOffset(page, limit)
	get.Order("a.publish_at ASC").Limit(limit).Offset(offset)
	get.Select("a.id, a.title, b.media, b.platform, b.channel_name, a.publish_at, a.verifier_id, a.state, a.media_id_manual, a.problem_area_manual, a.created_at, a.receive_at")
	if err = get.Find(&lists).Error; err != nil {
		return nil, err
	}
	{
		articleIds := make([]int64, 0)
		verifyIds := make([]int64, 0)
		for _, v := range lists {
			articleIds = append(articleIds, v.Id)
			if v.VerifierId > 0 {
				verifyIds = append(verifyIds, v.VerifierId)
			}
		}
		if len(verifyIds) > 0 {
			user := &User{}
			users := make([]*User, 0)
			err = db.Model(user).
				Where("id IN (?)", verifyIds).
				Select("id, real_name").
				Find(&users).
				Error
			if err != nil {
				return nil, err
			}
			userMap := make(map[int64]*User)
			for _, v := range users {
				userMap[v.Id] = v
			}
			for _, v := range lists {
				tmp, ok := userMap[v.VerifierId]
				if ok && tmp != nil {
					v.VerifierRealName = tmp.RealName
				}
			}
		}

		{
			get1 := db.Model(articleReader).
				Where("article_id IN (?)", articleIds).
				Select("max(id) as id")
			articleReaders := make([]*ArticleReader, 0)
			db.Model(articleReader).
				Where("id IN (?)", get1).
				Select("reader_at", "commit_at").
				Find(&articleReaders)
			arm := make(map[int64]*ArticleReader)
			for _, v := range articleReaders {
				arm[v.Id] = v
			}
			for _, v := range lists {
				tmp, ok := arm[v.Id]
				if !ok {
					continue
				}
				if tmp.ReaderAt > 0 {
					v.ReceiveAt = tmp.ReaderAt
				}
				if tmp.CommitAt > 0 {
					v.CommitAt = tmp.CommitAt
				}
			}
		}
		{
			records := make([]*ArticleRecords, 0)
			db.Model(&ArticleRecords{}).
				Where("article_id IN (?)", articleIds).
				Group("article_id,from_type").
				Select("count(*) as id", "article_id", "from_type").
				Find(&records)
			mp := make(map[int64]*ArticleRecords)
			for _, v := range records {
				mp[v.ArticleId] = v
			}
			for _, v := range lists {
				tmp, ok := mp[v.Id]
				if !ok {
					continue
				}
				if tmp.FromType == 1 {
					v.MachineCount = tmp.Id
				}
				if tmp.FromType == 2 {
					v.CommitRecordsCount = tmp.Id
				}
			}
		}
		{
			records := make([]*ArticleRecords, 0)
			db.Model(&ArticleRecords{}).
				Where("article_id IN (?) AND reader_score > 0", articleIds).
				Group("article_id").
				Select("COALESCE(sum(reader_score),0) as id", "article_id").
				Find(&records)
			mp := make(map[int64]*ArticleRecords)
			for _, v := range records {
				mp[v.ArticleId] = v
			}
			for _, v := range lists {
				tmp, ok := mp[v.Id]
				if !ok {
					continue
				}
				v.SumScores = tmp.Id
			}
		}
	}
	for _, v := range lists {
		tmp := &v1.GetArticleReaderArticleListsReply_Item{
			Id:                 v.Id,
			Title:              v.Title,
			Media:              v.Media,
			Platform:           v.Platform,
			ChannelName:        v.ChannelName,
			PublishAt:          v.PublishAt,
			ReceiveAt:          v.ReceiveAt,
			CommitAt:           v.CommitAt,
			MachineCount:       v.MachineCount,
			CommitRecordsCount: v.CommitRecordsCount,
			SumScores:          v.SumScores,
			VerifierId:         v.VerifierId,
			VerifierRealName:   v.VerifierRealName,
			State:              v.State,
			MediaIdManual:      v.MediaIdManual,
			ProblemAreaManual:  v.ProblemAreaManual,
			CreatedAt:          v.CreatedAt,
		}
		reply.Data = append(reply.Data, tmp)
	}
	return reply, nil
}

type articleReaderArticleRecordsListsItemDisposal struct {
	// id
	Id int64 `gorm:"column:id"`
	// 上级id
	Pid int64 `gorm:"column:pid"`
	// 用户id
	UserId int64 `gorm:"column:user_id"`
	// 差错id
	ArticleRecordsId int64 `gorm:"column:article_records_id"`
	// 处置记录描述
	Content *string `gorm:"column:content"`
	// 备注
	Note string `gorm:"column:note"`
	// 创建时间
	CreatedAt int64 `gorm:"column:created_at"`
}
type articleReaderArticleRecordsListsItem struct {
	// id
	Id int64 `gorm:"column:id"`
	// 标题
	Title *string `gorm:"column:title"`
	// 媒体名称
	Media *string `gorm:"column:media"`
	// 平台名称
	Platform *string `gorm:"column:platform"`
	// 频道名称
	ChannelName *string `gorm:"column:channel_name"`
	// 稿件id
	ArticleId *int64 `gorm:"column:article_id"`
	// 发布时间
	PublishAt *int64 `gorm:"column:publish_at"`
	// 差错来源类型 0:未知 1:机器巡检 2:审核员提交
	FromType int32 `gorm:"column:from_type"`
	// 审读员id
	ReaderId int64 `gorm:"column:reader_id"`
	// 审读员审读时间
	ReaderAt int64 `gorm:"column:reader_at"`
	// 审读员确认差错 -1:未确认 0:判定否 1:判定是
	ReaderCheck int32 `gorm:"column:reader_check"`
	// 复核员id
	VerifierId int64 `gorm:"column:verifier_id"`
	// 差错分类 0:未知 1:文本问题 2:图片问题 3:视频问题 7:其它问题
	ErrorCategory int32 `gorm:"column:error_category"`
	// 差错URL地址合集(存:图片URL地址,视频URL地址,人提问题截图URL地址)
	ErrorUrls *string `gorm:"column:error_urls"`
	// 差错位置(视频帧),例:标题
	ErrorLocation *string `gorm:"column:error_location"`
	// 差错类型,例:政治性差错
	ErrorType string `gorm:"column:error_type"`
	// 差错分类整型值
	ErrorTypeInt int32 `gorm:"column:error_type_int"`
	// 差错子类型,例:涉政人物
	ErrorSubtype string `gorm:"column:error_subtype"`
	// 差错原因,例:错别字,标点符号,少字错误,禁用词
	ErrorReason string `gorm:"column:error_reason"`
	// 差错描述语,例:标题出现禁用词XX
	ErrorMessage string `gorm:"column:error_message"`
	// 修改建议,例:建议更改为YY
	ErrorModifyAdvice *string `gorm:"column:error_modify_advice"`
	// 差错等级 0:未知 1:疑错 2:一般 3:严重
	ErrorLevel int32 `gorm:"column:error_level"`
	// 差错内容
	ErrorContent *string `gorm:"column:error_content"`
	// 视频截图URL地址合集
	ErrorVideoScreenshot *string `gorm:"column:error_video_screenshot"`
	// 审读员得分
	ReaderScore int32 `gorm:"column:reader_score"`
	// 复核员打分时间
	VerifierScoreAt int64 `gorm:"column:verifier_score_at"`
	// 是否误判 0:否,1:是
	Misjudged int32 `gorm:"column:misjudged"`
	// 置信度,如:1.23
	ConfidenceLevel float64 `gorm:"column:confidence_level"`
	// 结论类型
	ConclusionType int32 `gorm:"column:conclusion_type"`
	// 结论
	Conclusion string `gorm:"column:conclusion"`
	// 创建时间
	CreatedAt int64 `gorm:"column:created_at"`
	// 差错截图,截图url地址
	ErrorScreenshot *string `gorm:"column:error_screenshot"`
	// 审读员真实姓名
	ReaderRealName string `gorm:"column:reader_real_name"`
	// 复核员真实姓名
	VerifierRealName string `gorm:"column:verifier_real_name"`
	// 处置记录
	Disposal []*articleReaderArticleRecordsListsItemDisposal `gorm:"-"`
}

func (s *Articles) GetArticleReaderArticleRecordsLists(ctx context.Context, req *v1.GetArticleReaderArticleRecordsListsRequest) (*v1.GetArticleReaderArticleRecordsListsReply, error) {
	reply := &v1.GetArticleReaderArticleRecordsListsReply{}
	reply.StatusCode = 200
	reply.Message = "success"

	db := s.db.DB()
	article := &Article{}
	articleRecords := &ArticleRecords{}
	approveSources := &ApproveSources{}
	lists := make([]*articleReaderArticleRecordsListsItem, 0)

	subquery := db.Table(articleRecords.TableName())
	subquery.Where("reader_id = ?", req.GetUserId())
	if req.State != nil {
		subquery.Where("state = ?", *req.State)
	}
	if req.CreatedAtStart != nil && req.CreatedAtEnd != nil && *req.CreatedAtEnd > *req.CreatedAtStart && *req.CreatedAtStart > 0 {
		subquery.Where("created_at BETWEEN ? AND ?", *req.CreatedAtStart, *req.CreatedAtEnd)
	}
	if req.ErrorType != nil {
		subquery.Where("error_type = ?", *req.ErrorType)
	}
	if req.ErrorLevel != nil {
		subquery.Where("error_level = ?", *req.ErrorLevel)
	}
	if req.ErrorLocation != nil {
		subquery.Where("error_location = ?", *req.ErrorLocation)
	}
	subquery.Where("deleted_at = 0")
	get := db.Table("(?) AS a", subquery).
		Joins("LEFT JOIN " + article.TableName() + " b ON a.article_id = b.id").
		Joins("LEFT JOIN " + approveSources.TableName() + " c ON b.source_id = c.id")
	if req.GetKeyword() != "" {
		get.Where("b.title LIKE ?", fmt.Sprintf("%%%s%%", req.GetKeyword()))
	}
	if req.MediaId != nil {
		get.Where("c.media_id = ?", req.GetMediaId())
	}
	if req.PlatformId != nil {
		get.Where("c.platform_id = ?", req.GetPlatformId())
	}
	if req.ChannelName != nil {
		get.Where("c.channel_name = ?", req.GetChannelName())
	}

	ikv := make([]*KeyValueInt32Int64, 0)
	db.Table("(?) a", get.Select("from_type as category")).
		Select("category as k", "count(*) as v").
		Group("category").
		Scan(&ikv)
	reply.Categories = make(map[int32]int64)
	for _, v := range ikv {
		reply.Categories[v.K] = v.V
	}

	subquery.Where("from_type = ?", req.GetFromType())
	err := get.Select("*").Count(&reply.Total).Error
	if err != nil {
		return nil, err
	}
	reply.Data = make([]*v1.GetArticleReaderArticleRecordsListsReply_Item, 0)
	if reply.Total == 0 {
		return reply, nil
	}
	get.Select("a.id, b.title, c.media, c.platform, c.channel_name, b.id as article_id, b.publish_at, a.from_type, a.reader_id, a.reader_at, a.reader_check, a.verifier_id, a.error_category, a.error_urls, a.error_location, a.error_type, a.error_type_int, a.error_subtype, a.error_reason, a.error_message, a.error_modify_advice, a.error_level, a.error_content, a.error_video_screenshot, a.reader_score, a.verifier_score_at, a.misjudged, a.confidence_level, a.conclusion_type, a.conclusion, a.created_at")
	page := getPage(req.GetPage())
	limit := getLimit(req.GetLimit())
	offset := getOffset(page, limit)
	get.Order("a.id ASC").Limit(limit).Offset(offset)
	if err = get.Find(&lists).Error; err != nil {
		return nil, err
	}
	{
		userIds := make([]int64, 0)
		disposalIds := make([]int64, 0)
		for _, v := range lists {
			if v.VerifierId > 0 {
				userIds = append(userIds, v.VerifierId)
			}
			if v.ReaderId > 0 {
				userIds = append(userIds, v.VerifierId)
			}
			disposalIds = append(disposalIds, v.Id)
		}
		if len(userIds) > 0 {
			user := &User{}
			users := make([]*User, 0)
			err = db.Model(user).
				Where("id IN (?)", userIds).
				Select("id, real_name").
				Find(&users).
				Error
			if err != nil {
				return nil, err
			}
			userMap := make(map[int64]*User)
			for _, v := range users {
				userMap[v.Id] = v
			}
			for _, v := range lists {
				tmp, ok := userMap[v.VerifierId]
				if ok && tmp != nil {
					v.VerifierRealName = tmp.RealName
				}
				tmp, ok = userMap[v.ReaderId]
				if ok && tmp != nil {
					v.ReaderRealName = tmp.RealName
				}
			}
		}
		if len(disposalIds) > 0 {
			disposal := &ArticleRecordsDisposal{}
			disposals := make([]*articleReaderArticleRecordsListsItemDisposal, 0)
			err = db.Model(disposal).
				Where("article_records_id IN (?)", disposalIds).
				Find(&disposals).
				Error
			if err != nil {
				return nil, err
			}
			disposalMap := make(map[int64][]*articleReaderArticleRecordsListsItemDisposal)
			for _, v := range disposals {
				_, ok := disposalMap[v.ArticleRecordsId]
				if !ok {
					disposalMap[v.ArticleRecordsId] = make([]*articleReaderArticleRecordsListsItemDisposal, 0, 1)
				}
				disposalMap[v.ArticleRecordsId] = append(disposalMap[v.ArticleRecordsId], v)
			}
			for _, v := range lists {
				tmp, ok := disposalMap[v.Id]
				if ok {
					v.Disposal = tmp
				}
			}
		}
	}
	for _, v := range lists {
		tmp := &v1.GetArticleReaderArticleRecordsListsReply_Item{
			Id:                   v.Id,
			Title:                v.Title,
			Media:                v.Media,
			Platform:             v.Platform,
			ChannelName:          v.ChannelName,
			PublishAt:            v.PublishAt,
			FromType:             v.FromType,
			ReaderId:             v.ReaderId,
			ReaderAt:             v.ReaderAt,
			ReaderCheck:          v.ReaderCheck,
			VerifierId:           v.VerifierId,
			ErrorCategory:        v.ErrorCategory,
			ErrorUrls:            v.ErrorUrls,
			ErrorLocation:        v.ErrorLocation,
			ErrorType:            v.ErrorType,
			ErrorTypeInt:         v.ErrorTypeInt,
			ErrorSubtype:         v.ErrorSubtype,
			ErrorReason:          v.ErrorReason,
			ErrorMessage:         v.ErrorMessage,
			ErrorModifyAdvice:    v.ErrorModifyAdvice,
			ErrorLevel:           v.ErrorLevel,
			ErrorContent:         v.ErrorContent,
			ErrorVideoScreenshot: v.ErrorVideoScreenshot,
			ReaderScore:          v.ReaderScore,
			VerifierScoreAt:      v.VerifierScoreAt,
			Misjudged:            v.Misjudged,
			ConfidenceLevel:      v.ConfidenceLevel,
			ConclusionType:       v.ConclusionType,
			Conclusion:           v.Conclusion,
			CreatedAt:            v.CreatedAt,
			ReaderRealName:       v.ReaderRealName,
			VerifierRealName:     v.VerifierRealName,
			ArticleId:            v.ArticleId,
		}
		for _, w := range v.Disposal {
			tmp.Disposal = append(tmp.Disposal, &v1.GetArticleReaderArticleRecordsListsReply_Item_Disposal{
				Id:               w.Id,
				Pid:              w.Pid,
				UserId:           w.UserId,
				ArticleRecordsId: w.ArticleRecordsId,
				Content:          w.Content,
				Note:             w.Note,
				CreatedAt:        w.CreatedAt,
			})
		}
		if v.Disposal == nil {
			v.Disposal = make([]*articleReaderArticleRecordsListsItemDisposal, 0)
		}
		reply.Data = append(reply.Data, tmp)
	}
	return reply, nil
}

// readerActionVerify 撤回 退回 提交
func (s *Articles) readerActionVerify(
	ctx context.Context,
	articleId int64,
	userId int64,
	handle func(article *Article, articleReader *ArticleReader, user *User) error,
) (msg error, err error) {
	if articleId <= 0 {
		return errors.New("稿件id不合法"), nil
	}
	if userId <= 0 {
		return errors.New("非法请求"), nil
	}
	db := s.db.DB()
	articles := make([]*Article, 0)
	err = db.Model(&Article{}).
		WithContext(ctx).
		Where("id = ? AND deleted_at = 0", articleId).
		Limit(1).
		Find(&articles).Error
	if err != nil {
		return nil, err
	}
	if len(articles) == 0 {
		return errors.New("稿件信息不存在"), nil
	}

	articleReaders := make([]*ArticleReader, 0)
	err = db.Model(&ArticleReader{}).
		Where("article_id = ? AND reader_id = ? AND deleted_at = 0", articleId, userId).
		Limit(1).
		Order("id DESC").
		Find(&articleReaders).
		Error
	if err != nil {
		return nil, err
	}
	if len(articleReaders) == 0 {
		return errors.New("非法操作"), nil
	}

	users := make([]*User, 0)
	if err = db.Model(&User{}).
		WithContext(ctx).
		Where("id = ? AND deleted_at = 0", userId).
		Limit(1).
		Find(&users).Error; err != nil {
		return nil, err
	}
	if len(users) == 0 {
		return errors.New("账户不存在"), nil
	}
	if users[0].State != 1 {
		return errors.New("账户状态异常"), nil
	}
	msg = handle(articles[0], articleReaders[0], users[0])
	if msg != nil {
		return msg, nil
	}
	return nil, nil
}

// PostArticleReaderCancel 撤回
func (s *Articles) PostArticleReaderCancel(ctx context.Context, req *v1.PostArticleReaderCancelRequest) (*v1.PostArticleReaderCancelReply, error) {
	reply := &v1.PostArticleReaderCancelReply{}
	reply.StatusCode = 200
	reply.Message = "success"

	db := s.db.DB()
	timestamp := util.Timestamp()
	articleId := req.ArticleId
	userId := req.GetUserId()
	var articleReaderInfo *ArticleReader
	var articleObject *Article
	msg, err := s.readerActionVerify(ctx, articleId, userId, func(article *Article, articleReader *ArticleReader, user *User) error {
		if article.State != 9 && article.State != 17 {
			return errors.New("稿件状态不匹配(非审读中,待复核)")
		}
		if article.Category == 1 {
			if articleReader.State != 2 {
				return errors.New("只有已审读状态可以撤回")
			}
		}
		if article.DeadlineAt > 0 && article.DeadlineAt < timestamp {
			return errors.New("稿件已超时,不可撤回")
		}
		if articleReader.DeadlineAt > 0 && timestamp > articleReader.DeadlineAt {
			return errors.New("审读已超时,不可撤回")
		}
		articleReaderInfo = articleReader
		if article.Category == 1 {
			others := make([]*ArticleReader, 0)
			err := db.Model(&ArticleReader{}).
				Where("article_id = ? AND state = 1 AND reader_id <> ?", articleId, userId).
				Select("id").
				Find(&others).Error
			if err != nil {
				return err
			}
			if len(others) > 0 {
				return errors.New("当前稿件正在被他人审读")
			}
		}
		articleObject = article
		return nil
	})
	if err != nil {
		return nil, err
	}
	if msg != nil {
		reply.StatusCode = 500
		reply.Message = msg.Error()
		return reply, nil
	}
	_ = db.Transaction(func(tx *gorm.DB) error {
		if articleObject.Category == 1 {
			err = tx.Model(&ArticleRecords{}).
				Where("article_id = ? AND reader_id = ? AND reader_draft = 0", articleId, userId).
				Where("( from_type = 1 AND reader_check = 1 ) OR from_type = 2").
				Updates(map[string]interface{}{
					"reader_draft": 1,
					"updated_at":   timestamp,
				}).Error
			if err != nil {
				return err
			}
			err = tx.Model(&ArticleReader{}).
				Where("id = ? AND state = 2", articleReaderInfo.Id).
				Updates(map[string]interface{}{
					"state":      1,
					"updated_at": timestamp,
				}).Error
			if err != nil {
				return err
			}
			err = tx.Model(&Article{}).
				Where("id = ? AND state = 17", articleId).
				Updates(map[string]interface{}{
					"state":      9,
					"claimed":    0,
					"updated_at": timestamp,
				}).Error
			if err != nil {
				return err
			}
		}
		if articleObject.Category == 2 {
			err = tx.Model(&Article{}).
				Where("id = ? AND state = 17", articleId).
				Updates(map[string]interface{}{
					"state":      9,
					"updated_at": timestamp,
				}).Error
			if err != nil {
				return err
			}
			err = tx.Model(&ArticleReader{}).
				Where("id = ? AND state = 4", articleReaderInfo.Id).
				Updates(map[string]interface{}{
					"state":      2,
					"updated_at": timestamp,
				}).Error
			if err != nil {
				return err
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return reply, nil
}

// PostArticleReaderRefunded 退回
func (s *Articles) PostArticleReaderRefunded(ctx context.Context, req *v1.PostArticleReaderRefundedRequest) (*v1.PostArticleReaderRefundedReply, error) {
	reply := &v1.PostArticleReaderRefundedReply{}
	reply.StatusCode = 200
	reply.Message = "success"

	db := s.db.DB()
	timestamp := util.Timestamp()
	articleId := req.ArticleId
	userId := req.GetUserId()
	integral := int64(5)
	var userinfo *User
	var articleReaderInfo *ArticleReader
	msg, err := s.readerActionVerify(ctx, articleId, userId, func(article *Article, articleReader *ArticleReader, user *User) error {
		if article.State != 9 {
			return errors.New("稿件状态不是审读中")
		}
		if articleReader.State != 1 {
			return errors.New("审读状态不是审读中")
		}
		if article.DeadlineAt > 0 && article.DeadlineAt < timestamp {
			return errors.New("稿件已超时,不可退回")
		}
		if articleReader.DeadlineAt > 0 && timestamp > articleReader.DeadlineAt {
			return errors.New("审读超时")
		}
		articleReaderInfo = articleReader
		userinfo = user
		tmp, err := s.basic.getCfgInt("B02")
		if err != nil {
			s.logger.Log(log.LevelWarn, "error", err.Error())
			return err
		}
		integral = tmp
		return nil
	})
	if err != nil {
		return nil, err
	}
	if msg != nil {
		reply.StatusCode = 500
		reply.Message = msg.Error()
		return reply, nil
	}
	userIntegral := &UserIntegral{
		Category:  1,
		State:     9,
		StateDesc: "已完成",
	}
	userIntegral.UserId = userId
	userIntegral.ChangeBefore = userinfo.Integral
	userIntegral.ChangeAmount = 0 - integral
	userIntegral.ChangeAfter = userinfo.Integral - integral
	userIntegral.Note = "退回扣除积分"
	userIntegral.AssocId = articleId
	userIntegral.CreatedAt = timestamp
	userIntegral.UpdatedAt = timestamp
	_ = db.Transaction(func(tx *gorm.DB) error {
		err = tx.Model(&ArticleReader{}).
			Where("id = ? AND state = 1", articleReaderInfo.Id).
			Updates(map[string]interface{}{
				"state":      7,
				"updated_at": timestamp,
			}).Error
		if err != nil {
			return err
		}
		err = tx.Model(&Article{}).
			Where("id = ?", articleId).
			Updates(map[string]interface{}{
				"claimed":    0,
				"updated_at": timestamp,
			}).Error
		if err != nil {
			return err
		}
		err = s.updateUserIntegral(context.Background(), tx, userinfo, userIntegral)
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return reply, nil
}

// PostArticleReaderSubmit 提交
func (s *Articles) PostArticleReaderSubmit(ctx context.Context, req *v1.PostArticleReaderSubmitRequest) (*v1.PostArticleReaderSubmitReply, error) {
	reply := &v1.PostArticleReaderSubmitReply{}
	reply.StatusCode = 200
	reply.Message = "success"

	db := s.db.DB()
	timestamp := util.Timestamp()
	articleId := req.ArticleId
	userId := req.GetUserId()
	var articleObject *Article
	var userObject *User
	msg, err := s.readerActionVerify(ctx, articleId, userId, func(article *Article, articleReader *ArticleReader, user *User) error {
		if article.State != 9 {
			return errors.New("稿件状态被更新")
		}
		if article.Category == 1 {
			if articleReader.State != 1 {
				return errors.New("审读状态被更新")
			}
		}
		if user.IsVerifier != 1 {
			if article.DeadlineAt > 0 && article.DeadlineAt < timestamp {
				return errors.New("稿件已超时,不可提交")
			}
			if articleReader.DeadlineAt > 0 && articleReader.DeadlineAt < timestamp {
				return errors.New("审读超时")
			}
		}
		articleObject = article
		userObject = user
		return nil
	})
	if err != nil {
		return nil, err
	}
	if msg != nil {
		reply.StatusCode = 500
		reply.Message = msg.Error()
		return reply, nil
	}
	_ = db.Transaction(func(tx *gorm.DB) error {
		if articleObject.Category == 1 {
			articleRecordsUpdate := map[string]interface{}{
				"reader_draft": 0,
				"updated_at":   timestamp,
			}
			err = tx.Model(&ArticleRecords{}).
				Where("article_id = ? AND reader_id = ?", articleId, userId).
				Where("( ( from_type = 1 AND reader_check = 1 ) OR from_type = 2 )").
				Where("deleted_at = 0").
				Updates(articleRecordsUpdate).Error
			if err != nil {
				return err
			}
			articleReaderUpdate := map[string]interface{}{
				"state":      2,
				"updated_at": timestamp,
			}
			if userObject.IsVerifier == 1 {
				articleReaderUpdate["state"] = 6
			}
			err = tx.Model(&ArticleReader{}).
				Where("article_id = ? AND reader_id = ?", articleId, userId).
				Where("deleted_at = 0").
				Updates(articleReaderUpdate).Error
			if err != nil {
				return err
			}
			articleUpdate := map[string]interface{}{
				"claimed":    0,
				"updated_at": timestamp,
			}
			if userObject.IsVerifier == 1 {
				articleUpdate["state"] = 25
			}
			err = tx.Model(&Article{}).
				Where("id = ?", articleId).
				Updates(articleUpdate).Error
			if err != nil {
				return err
			}
		}
		if articleObject.Category == 2 {
			articleUpdate := map[string]interface{}{
				"state":      17,
				"updated_at": timestamp,
			}
			if userObject.IsVerifier == 1 {
				articleUpdate["state"] = 25
			}
			err = tx.Model(&Article{}).
				Where("id = ?", articleId).
				Updates(articleUpdate).Error
			if err != nil {
				return err
			}
			articleReaderUpdate := map[string]interface{}{
				"state":      4,
				"updated_at": timestamp,
			}
			if userObject.IsVerifier == 1 {
				articleReaderUpdate["state"] = 6
			}
			err = tx.Model(&ArticleReader{}).
				Where("article_id = ? AND reader_id = ?", articleId, userId).
				Where("deleted_at = 0").
				Updates(articleReaderUpdate).Error
			if err != nil {
				return err
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	if userObject.IsVerifier != 1 {
		go func() {
			_ = s.AfterReaderCommit(articleId)
		}()
	}
	return reply, nil
}

func (s *Articles) GetWithdrawLists(ctx context.Context, req *v1.GetWithdrawListsRequest) (*v1.GetWithdrawListsReply, error) {
	reply := &v1.GetWithdrawListsReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	reply.Data = make([]*v1.GetWithdrawListsReply_Item, 0)

	table := &UserWithdraw{}
	db := s.db.DB()

	query := db.Table(table.TableName()).
		Where("category = 3").
		Where("user_id = ?", req.GetUserId())
	err := query.Count(&reply.Total).Error
	if err != nil {
		return nil, err
	}
	page := getPage(req.GetPage())
	limit := getLimit(req.GetLimit())
	offset := getOffset(page, limit)
	query.Order("id DESC").Limit(limit).Offset(offset)
	items := make([]*UserWithdraw, 0)
	err = query.Scan(&items).Error
	if err != nil {
		return nil, err
	}
	for _, v := range items {
		w := &v1.GetWithdrawListsReply_Item{
			Id:           v.Id,
			ChangeBefore: v.ChangeBefore,
			ChangeAmount: v.ChangeAmount,
			ChangeAfter:  v.ChangeAfter,
			Note:         v.Note,
			State:        v.State,
			StateDesc:    v.StateDesc,
			CreatedAt:    v.CreatedAt,
		}
		reply.Data = append(reply.Data, w)
	}
	return reply, nil
}

func (s *Articles) PostWithdraw(ctx context.Context, req *v1.PostWithdrawRequest) (*v1.PostWithdrawReply, error) {
	reply := &v1.PostWithdrawReply{}
	reply.StatusCode = 200
	reply.Message = "success"

	if req.Integral <= 0 {
		reply.StatusCode = 500
		reply.Message = "提现积分不合法"
		return reply, nil
	}

	db := s.db.DB()
	timestamp := util.Timestamp()

	user := &User{}
	userList := make([]*User, 0)
	err := db.Model(user).
		WithContext(ctx).
		Where("id = ? AND deleted_at = 0", req.GetUserId()).
		Limit(1).
		Find(&userList).Error
	if err != nil {
		return nil, err
	}
	if len(userList) == 0 {
		return nil, errors.New("账户不存在")
	}
	first1 := userList[0]
	if first1.State != 1 {
		return nil, errors.New("账户状态异常")
	}
	if first1.Integral-req.Integral < 0 {
		reply.StatusCode = 500
		reply.Message = "积分余额不足"
		return reply, nil
	}

	now := time.Now()
	monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.Local)

	// 针对当前用户的提现限制
	{
		// 最低提现分数
		minWithdrawScore, err := s.basic.getCfgInt("B0A")
		if err != nil {
			return nil, err
		}
		if req.Integral < minWithdrawScore {
			reply.StatusCode = 500
			reply.Message = fmt.Sprintf("提现金额不能低于%d", minWithdrawScore)
			return reply, nil
		}
		withdraws := make([]*UserWithdraw, 0)
		err = db.Model(&UserWithdraw{}).
			Where("user_id = ?", req.UserId).
			Where("category = 1").
			Where("created_at BETWEEN ? AND ?", monthStart.Unix(), now.Unix()).
			Find(&withdraws).
			Error
		if err != nil {
			return nil, err
		}
		// 每人每月最多提现次数
		thisMonthMaxTimes, err := s.basic.getCfgInt("B0B")
		if err != nil {
			return nil, err
		}
		if int64(len(withdraws)) >= thisMonthMaxTimes {
			reply.StatusCode = 500
			reply.Message = fmt.Sprintf("每人每月最多提现%d次", thisMonthMaxTimes)
			return reply, nil
		}
		thisMonthWithdrawsScores := int64(0)
		for _, v := range withdraws {
			changeAmount := v.ChangeAmount
			if changeAmount < 0 {
				changeAmount = 0 - changeAmount
			}
			thisMonthWithdrawsScores += changeAmount
		}
		// 每人每月最多提现xxx积分
		thisMonthMaxWithdrawsScores, err := s.basic.getCfgInt("B0A")
		if err != nil {
			return nil, err
		}
		if thisMonthWithdrawsScores >= thisMonthMaxWithdrawsScores {
			reply.StatusCode = 500
			reply.Message = fmt.Sprintf("每人每月最多提现%d积分", thisMonthMaxWithdrawsScores)
			return reply, nil
		}
	}

	// 针对全平台的提现限制判断
	msg, err := func() (error, error) {
		s.withdrawIntegral.Lock()
		defer s.withdrawIntegral.Unlock()

		thisMonthAllWithdraw := make([]*UserWithdraw, 0)
		err = db.Model(&UserWithdraw{}).
			Where("category = 1").
			Where("created_at BETWEEN ? AND ?", monthStart.Unix(), now.Unix()).
			Select("COALESCE(SUM(change_amount),0) as change_amount").
			Find(&thisMonthAllWithdraw).
			Error
		if err != nil {
			return nil, err
		}
		thisMonthAllWithdrawScores := int64(0)
		if len(thisMonthAllWithdraw) > 0 {
			thisMonthAllWithdrawScores = thisMonthAllWithdraw[0].ChangeAmount
			if thisMonthAllWithdrawScores < 0 {
				thisMonthAllWithdrawScores = 0 - thisMonthAllWithdrawScores
			}
		}
		// 每月限提总积分
		thisMonthScores, err := s.basic.getCfgInt("B08")
		if err != nil {
			return nil, err
		}
		if thisMonthAllWithdrawScores+req.Integral > thisMonthScores {
			return errors.New("系统提现可用金额不足"), nil
		}
		return nil, nil
	}()
	if err != nil {
		return nil, err
	}
	if msg != nil {
		reply.StatusCode = 500
		reply.Message = msg.Error()
		return reply, nil
	}

	// 开始提现
	create := &UserWithdraw{
		Category:     1,
		ChangeBefore: first1.Integral,
		ChangeAmount: 0 - req.Integral,
		ChangeAfter:  first1.Integral - req.Integral,
		State:        1,
		StateDesc:    "待审批",
	}
	create.UserId = req.GetUserId()
	create.CreatedAt = timestamp
	create.UpdatedAt = timestamp
	userIntegral := &UserIntegral{
		Category:     2,
		AssocId:      0,
		ChangeBefore: first1.Integral,
		ChangeAmount: 0 - req.Integral,
		ChangeAfter:  first1.Integral - req.Integral,
		State:        1,
		StateDesc:    "待处理",
	}
	userIntegral.UserId = create.UserId
	userIntegral.Note = "提现"
	userIntegral.CreatedAt = timestamp
	userIntegral.UpdatedAt = timestamp

	_ = db.Transaction(func(tx *gorm.DB) error {
		err = tx.Model(&UserWithdraw{}).Create(create).Error
		if err != nil {
			return err
		}
		userIntegral.AssocId = create.Id
		err = s.updateUserIntegral(
			context.Background(),
			tx,
			user,
			userIntegral,
		)
		if err != nil {
			s.logger.Log(log.LevelWarn, "error", err.Error())
			err = errors.New("积分扣减失败,请重试")
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return reply, nil
}

func (s *Articles) GetReaderWorkbench1(ctx context.Context, req *v1.GetReaderWorkbench1Request) (*v1.GetReaderWorkbench1Reply, error) {
	reply := &v1.GetReaderWorkbench1Reply{}
	reply.StatusCode = 200
	reply.Message = "success"
	data := &v1.GetReaderWorkbench1Reply_Item{}
	reply.Data = data
	userId := req.GetUserId()
	startAt, endAt := req.GetCreatedAtStart(), req.GetCreatedAtEnd()
	atOk := startAt > 0 && startAt < endAt
	db := s.db.DB()
	articleReader := &ArticleReader{}
	{
		get := db.Model(articleReader).
			Where("reader_id = ?", userId).
			Group("article_id").
			Select("article_id")
		if atOk {
			get.Where("created_at BETWEEN ? AND ?", startAt, endAt)
		}
		db.Table("(?) a", get).Count(&data.SumClaimed)
	}
	{
		get := db.Model(articleReader).
			Where("reader_id = ?", userId).
			Where("state IN (?)", []int{2, 4, 6}).
			Group("article_id").
			Select("article_id")
		if atOk {
			get.Where("created_at BETWEEN ? AND ?", startAt, endAt)
		}
		db.Table("(?) a", get).Count(&data.SumCommited)
	}
	{
		get := db.Model(articleReader).
			Where("reader_id = ?", userId).
			Where("state = 6").
			Group("article_id").
			Select("article_id")
		if atOk {
			get.Where("created_at BETWEEN ? AND ?", startAt, endAt)
		}
		db.Table("(?) a", get).Count(&data.SumClosed)
	}
	articleRecords := &ArticleRecords{}
	{
		get := db.Model(articleRecords).
			Where("reader_id = ?", userId)
		if atOk {
			get.Where("created_at BETWEEN ? AND ?", startAt, endAt)
		}
		db.Table("(?) a", get).Count(&data.SumRecord)
	}
	{
		get := db.Model(articleRecords).
			Where("reader_id = ?", userId).
			Where("misjudged = 1")
		if atOk {
			get.Where("created_at BETWEEN ? AND ?", startAt, endAt)
		}
		db.Table("(?) a", get).Count(&data.SumMisjudged)
	}
	userIntegral := &UserIntegral{}
	{
		get := db.Model(userIntegral).
			Where("user_id = ?", userId).
			Where("category = 1")
		if atOk {
			get.Where("created_at BETWEEN ? AND ?", startAt, endAt)
		}
		get.Select("COALESCE(SUM(change_amount),0)").Scan(&data.SumScore)
	}
	{
		get := db.Model(userIntegral).
			Where("user_id = ?", userId).
			Where("category = 2")
		if atOk {
			get.Where("created_at BETWEEN ? AND ?", startAt, endAt)
		}
		get.Select("COALESCE(SUM(change_amount),0)").Scan(&data.SumWithdraw)
		if data.SumWithdraw < 0 {
			data.SumWithdraw = 0 - data.SumWithdraw
		}
	}
	return reply, nil
}

type readerWorkbench2 struct {
	TimeAt string `gorm:"column:time_at"`
	Count  int64  `gorm:"column:counts"`
}

func (s *Articles) GetReaderWorkbench2(ctx context.Context, req *v1.GetReaderWorkbench2Request) (*v1.GetReaderWorkbench2Reply, error) {
	reply := &v1.GetReaderWorkbench2Reply{}
	reply.StatusCode = 200
	reply.Message = "success"
	data := make([]*v1.GetReaderWorkbench2Reply_Item, 0)
	userId := req.GetUserId()
	now := time.Now()
	startAt, endAt := int64(0), int64(0)

	db := s.db.DB()
	articleReader := &ArticleReader{}
	get1 := db.Model(articleReader).
		Where("reader_id = ?", userId)
	get2 := db.Model(articleReader).
		Where("reader_id = ?", userId)

	category := req.GetCategory()
	switch category {
	case 2: // 本月
		start := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
		startAt = start.Unix()
		endAt = now.Unix()
		get1.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%d') AS time_at")
		get2.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%d') AS time_at")
	case 3: // 近七日
		start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
		startAt = start.Unix() - 6*86400
		endAt = now.Unix()
		get1.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%d') AS time_at")
		get2.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%d') AS time_at")
	default: // 本年
		start := time.Date(now.Year(), time.January, 1, 0, 0, 0, 0, now.Location())
		startAt = start.Unix()
		endAt = now.Unix()
		get1.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%m') AS time_at")
		get2.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%m') AS time_at")
	}
	get1.Where("created_at BETWEEN ? AND ?", startAt, endAt)
	get2.Where("created_at BETWEEN ? AND ?", startAt, endAt)

	get2.Where("state = 6")

	get1.Where("deleted_at = 0")
	get2.Where("deleted_at = 0")

	// 忽略根据稿件去重(原因:每个人只能领取一次稿件)

	lists1 := make([]*readerWorkbench2, 0)
	lists2 := make([]*readerWorkbench2, 0)
	db.Table("(?) a", get1).Select("COUNT(*) as counts", "time_at").Group("time_at").Find(&lists1)
	db.Table("(?) a", get2).Select("COUNT(*) as counts", "time_at").Group("time_at").Find(&lists2)

	map1 := make(map[string]*readerWorkbench2)
	map2 := make(map[string]*readerWorkbench2)
	for _, v := range lists1 {
		map1[v.TimeAt] = v
	}
	for _, v := range lists2 {
		map2[v.TimeAt] = v
	}

	switch category {
	case 2:
		for i := 1; i <= now.Day(); i++ {
			data = append(data, &v1.GetReaderWorkbench2Reply_Item{
				Time: fmt.Sprintf("%d", i),
			})
		}
	case 3:
		for i := startAt; ; i += 86400 {
			if i > endAt {
				break
			}
			t := time.Unix(i, 0)
			data = append(data, &v1.GetReaderWorkbench2Reply_Item{
				Time: fmt.Sprintf("%d", t.Day()),
			})
		}
	default:
		for i := 1; i <= 12; i++ {
			data = append(data, &v1.GetReaderWorkbench2Reply_Item{
				Time: fmt.Sprintf("%d", i),
			})
		}
	}

	for _, v := range data {
		completed, ok := map2[v.Time]
		if ok {
			v.SumCompleted = completed.Count
		}
		claimed, ok := map1[v.Time]
		if ok {
			v.SumClaimed = claimed.Count
		} else {
			if completed != nil {
				v.SumClaimed = completed.Count
			}
		}
	}
	reply.Data = data
	return reply, nil
}

func (s *Articles) PostArticleDelete(ctx context.Context, req *v1.PostArticleDeleteRequest) (*v1.PostArticleDeleteReply, error) {
	reply := &v1.PostArticleDeleteReply{}
	reply.StatusCode = 200
	reply.Message = "success"

	timestamp := util.Timestamp()
	db := s.db.DB()
	article := &Article{}
	var err error
	_ = db.Transaction(func(tx *gorm.DB) error {
		err = tx.Model(article).Where("id = ? AND category = 2 AND reader_id = ?", req.ArticleId, req.GetUserId()).Updates(map[string]any{
			"updated_at": timestamp,
			"deleted_at": timestamp,
		}).Error
		if err != nil {
			return err
		}
		err = tx.Model(&ArticleReader{}).Where("article_id = ?", req.ArticleId).Updates(map[string]any{
			"updated_at": timestamp,
			"deleted_at": timestamp,
		}).Error
		if err != nil {
			return err
		}
		err = tx.Model(&ArticleRecords{}).Where("article_id = ?", req.ArticleId).Updates(map[string]any{
			"updated_at": timestamp,
			"deleted_at": timestamp,
		}).Error
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return reply, nil
}

func (s *Articles) PostArticleRecordsDelete(ctx context.Context, req *v1.PostArticleRecordsDeleteRequest) (*v1.PostArticleRecordsDeleteReply, error) {
	reply := &v1.PostArticleRecordsDeleteReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	timestamp := util.Timestamp()
	db := s.db.DB()
	article := &ArticleRecords{}
	err := db.Model(article).
		WithContext(ctx).
		Where("id = ? AND reader_id = ? AND from_type IN ( 2, 3 )", req.ArticleRecordsId, req.GetUserId()).
		Updates(map[string]any{
			"updated_at": timestamp,
			"deleted_at": timestamp,
		}).Error
	if err != nil {
		return nil, err
	}
	return reply, nil
}

/* 复核员 */

// likeTopic 专题模糊搜索
func (s *Articles) likeTopic(topicId int64, db *gorm.DB, column string) {
	if topicId <= 0 || db == nil || column == "" {
		return
	}
	topics := make([]*Topic, 0)
	s.db.DB().Model(&Topic{}).Where("topic_id = ?", topicId).Limit(1).Find(&topics)
	if len(topics) == 0 {
		return
	}
	keywords := strings.Split(topics[0].Keyword, ",")
	length := len(keywords)
	result := make([]string, 0, length)
	resultMap := make(map[string]*struct{}, length)
	for _, v := range keywords {
		v = strings.TrimSpace(v)
		if v == "" {
			continue
		}
		if _, ok := resultMap[v]; ok {
			continue
		}
		result = append(result, v)
		resultMap[v] = nil
	}
	if len(result) == 0 {
		return
	}
	for k, v := range result {
		result[k] = fmt.Sprintf("%s LIKE '%%%s%%'", column, v)
	}
	where := strings.Join(result, " OR ")
	db.Where(where)
}

func (s *Articles) GetArticleVerifierTaskList(ctx context.Context, req *v1.GetArticleVerifierTaskListRequest) (*v1.GetArticleVerifierTaskListReply, error) {
	reply := &v1.GetArticleVerifierTaskListReply{}
	reply.StatusCode = 200
	reply.Message = "success"

	db := s.db.DB()
	sourcesIds := make([]int64, 0)
	// 允许的信源
	err := db.Table((&UserApproveSources{}).TableName()+" a").
		Joins("INNER JOIN "+(&ApproveSources{}).TableName()+" b ON a.approve_sources_id = b.id").
		Where("a.user_id = ?", req.UserId).
		Select("b.id").
		Find(&sourcesIds).
		Error
	if err != nil {
		return nil, err
	}
	if len(sourcesIds) == 0 {
		reply.Data = make([]*v1.GetArticleVerifierTaskListReply_Data, 0)
		reply.Categories = make(map[int32]int64)
		return reply, nil
	}

	table := db.Model(&Article{})
	// table
	if req.Profile != nil && *req.Profile {
		table.Where("verifier_id = ?", req.UserId)
	} else {
		table.Where("category = 1")
		if req.NoVerifier != nil && *req.NoVerifier {
			table.Where("verifier_id = 0")
		}
		table.Where("source_id IN (?)", sourcesIds)
	}
	table.Where("deleted_at = 0")
	if req.PublishAtStart != nil && req.PublishAtEnd != nil && *req.PublishAtStart > 0 && *req.PublishAtEnd > *req.PublishAtStart {
		table.Where("publish_at BETWEEN ? AND ?", *req.PublishAtStart, *req.PublishAtEnd)
	}
	if req.CreatedAtStart != nil && req.CreatedAtEnd != nil && *req.CreatedAtStart > 0 && *req.CreatedAtEnd > *req.CreatedAtStart {
		table.Where("created_at BETWEEN ? AND ?", *req.CreatedAtStart, *req.CreatedAtEnd)
	}
	if req.MediaId != nil || req.PlatformId != nil || req.ChannelName != nil {
		sources := db.Model(&ApproveSources{})
		if req.MediaId != nil {
			sources.Where("media_id = ?", req.GetMediaId())
		}
		if req.PlatformId != nil {
			sources.Where("platform_id = ?", req.GetPlatformId())
		}
		if req.ChannelName != nil {
			sources.Where("channel_name = ?", req.GetChannelName())
		}
		sources.Select("id")
		table.Where("source_id IN (?)", sources)
	}
	if req.Keyword != nil && *req.Keyword != "" {
		table.Where("title LIKE ?", fmt.Sprintf("%%%s%%", *req.Keyword))
	}
	if req.Reader != nil && *req.Reader != "" {
		articleReaders := db.Table((&ArticleReader{}).TableName()+" a").
			Joins("LEFT JOIN "+(&User{}).TableName()+" b ON a.reader_id = b.id").
			Where("b.real_name LIKE ?", fmt.Sprintf("%%%s%%", *req.Reader)).
			Select("a.article_id").
			Group("a.article_id")
		table.Where("id IN (?)", articleReaders)
	}
	if req.TopicId != nil {
		s.likeTopic(req.GetTopicId(), table, "keyword")
	}

	// if req.Profile != nil && *req.Profile {
	// 	ikv := make([]*KeyValueInt32Int64, 0)
	// 	db.Table("(?) a", table.Select("category")).
	// 		Select("category as k", "count(*) as v").
	// 		Group("category").
	// 		Scan(&ikv)
	// 	reply.Categories = make(map[int32]int64)
	// 	for _, v := range ikv {
	// 		reply.Categories[v.K] = v.V
	// 	}
	// } else {
	ikv := make([]*KeyValueInt32Int64, 0)
	db.Table("(?) a", table.Select("state")).
		Select("state as k", "count(*) as v").
		Group("state").
		Scan(&ikv)
	reply.Categories = make(map[int32]int64)
	for _, v := range ikv {
		reply.Categories[v.K] = v.V
	}
	// }

	if req.State != nil {
		table.Where("state = ?", req.State)
	}
	err = table.Select("*").Count(&reply.Total).Error
	if err != nil {
		return nil, err
	}

	articles := make([]*Article, 0)
	page := getPage(req.GetPage())
	limit := getLimit(req.GetLimit())
	offset := getOffset(page, limit)
	table.Order("deadline_at ASC").Limit(limit).Offset(offset)
	table.Select("id", "title", "publish_at", "source_url", "created_at", "source_id", "state", "deadline_at", "verifier_id")
	err = table.Find(&articles).Error
	if err != nil {
		return nil, err
	}
	if len(articles) == 0 {
		reply.Data = make([]*v1.GetArticleVerifierTaskListReply_Data, 0)
		return reply, nil
	}
	articleIds := make([]int64, 0)
	sourcesIdsMap := make(map[int64]*struct{})
	userId := make([]int64, 0)
	for _, v := range articles {
		articleIds = append(articleIds, v.Id)
		if _, ok := sourcesIdsMap[v.SourceId]; !ok {
			sourcesIds = append(sourcesIds, v.SourceId)
			sourcesIdsMap[v.SourceId] = nil
		}
		if v.VerifierId > 0 {
			userId = append(userId, v.VerifierId)
		}
	}
	sources := make([]*ApproveSources, 0)
	db.Model(&ApproveSources{}).
		Where("id IN (?)", sourcesIds).
		Select("id", "media", "platform", "channel_name").
		Find(&sources)
	sourcesMap := make(map[int64]*ApproveSources)
	for _, v := range sources {
		sourcesMap[v.Id] = v
	}
	articleRecords := make([]*ArticleRecords, 0)
	db.Model(&ArticleRecords{}).
		Where("article_id IN (?) AND deleted_at = 0", articleIds).
		Select("article_id", "count(*) AS id", "COALESCE(sum(reader_score)) as reader_score").
		Group("article_id").
		Find(&articleRecords)
	articleRecordsMap := make(map[int64]*ArticleRecords)
	for _, v := range articleRecords {
		articleRecordsMap[v.ArticleId] = v
	}
	readers := make([]*User, 0)
	db.Table((&ArticleReader{}).TableName()+" a").
		Joins("LEFT JOIN "+(&User{}).TableName()+" b ON a.reader_id = b.id").
		Where("b.id IS NOT NULL").
		Select("a.article_id as id", "b.real_name", "a.reader_at as created_at").
		Order("id ASC").
		Find(&readers)
	readersMap := make(map[int64][]string)
	readersMapMap := make(map[int64]map[string]*struct{})
	readersArticleReceiveAtMap := make(map[int64]int64) // 稿件最后领取时间
	for _, v := range readers {
		if _, ok := readersMap[v.Id]; !ok {
			readersMap[v.Id] = make([]string, 0, 1)
			readersMapMap[v.Id] = make(map[string]*struct{}, 1)
		}
		if v.CreatedAt > 0 {
			readersArticleReceiveAtMap[v.Id] = v.CreatedAt
		}
		if _, ok := readersMapMap[v.Id][v.RealName]; ok {
			continue
		}
		readersMap[v.Id] = append(readersMap[v.Id], v.RealName)
		readersMapMap[v.Id][v.RealName] = nil
	}
	usersMap := make(map[int64]*User)
	if len(userId) > 0 {
		users := make([]*User, 0)
		db.Model(&User{}).Where("id IN (?)", userId).Find(&users)
		for _, v := range users {
			usersMap[v.Id] = v
		}
	}
	data := make([]*v1.GetArticleVerifierTaskListReply_Data, 0, len(articles))
	for _, v := range articles {
		w := &v1.GetArticleVerifierTaskListReply_Data{
			Id:         v.Id,
			Title:      v.Title,
			PublishAt:  v.PublishAt,
			SourceUrl:  v.SourceUrl,
			CreatedAt:  v.CreatedAt,
			State:      v.State,
			DeadlineAt: v.DeadlineAt,
			VerifierId: v.VerifierId,
		}
		if tmp, ok := sourcesMap[v.SourceId]; ok {
			w.Media = &tmp.Media
			w.Platform = &tmp.Platform
			w.ChannelName = &tmp.ChannelName
		}
		if tmp, ok := articleRecordsMap[v.Id]; ok {
			w.Records = tmp.Id
			w.TotalScore = int64(tmp.ReaderScore)
		}
		if tmp, ok := readersMap[v.Id]; ok {
			w.Readers = tmp
		}
		if tmp, ok := readersArticleReceiveAtMap[v.Id]; ok {
			w.ReceiveAt = tmp
		}
		if tmp, ok := usersMap[v.VerifierId]; ok {
			w.VerifierRealName = tmp.RealName
		}
		data = append(data, w)
	}
	reply.Data = data
	return reply, nil
}

type getArticleVerifierTaskDetails struct {
	ArticleRecords
	ReaderName string `gorm:"reader_name"`
}

func (s *Articles) GetArticleVerifierTaskDetails(ctx context.Context, req *v1.GetArticleVerifierTaskDetailsRequest) (*v1.GetArticleVerifierTaskDetailsReply, error) {
	reply := &v1.GetArticleVerifierTaskDetailsReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	reply.Data = &v1.GetArticleVerifierTaskDetailsReply_Data{}
	article := &Article{}
	db := s.db.DB()
	timestamp := util.Timestamp()
	db.Model(&Article{}).
		Where("id = ? AND verifier_id = 0", req.ArticleId).
		Updates(map[string]any{
			"verifier_id": req.UserId,
			"verifier_at": timestamp,
			"updated_at":  timestamp,
		})
	lists := make([]*articleReaderTaskDetails, 0)
	err := db.Table(article.TableName()).
		Joins("a LEFT JOIN approve_sources b ON a.source_id = b.id").
		WithContext(ctx).
		Where("a.id = ?", req.ArticleId).
		Where("a.deleted_at = 0").
		Select("a.id, a.title, a.content, a.keyword, a.category, a.reader_id, a.reader_at, a.publish_at, a.deadline_at, a.editor, a.editor_charge, a.editor_main, a.platform_editor, a.platform_editor_charge, a.platform_editor_main, a.source_url, a.briefing, a.created_at, a.state, a.media_id_manual, a.problem_area_manual, a.verifier_id, b.media_id, b.media, b.platform_id, b.platform, b.channel_name").
		Limit(1).
		Find(&lists).Error
	if err != nil {
		return nil, err
	}
	if len(lists) == 0 {
		reply.StatusCode = 500
		reply.Message = "稿件不存在"
		return reply, nil
	}
	v := lists[0]
	{
		readers := make([]*ArticleReader, 0)
		db.Model(&ArticleReader{}).Where("article_id = ? AND reader_id = ? AND state = 1", req.ArticleId, req.GetUserId()).Find(&readers)
		if len(readers) > 0 {
			v.ReaderAt = readers[0].ReaderAt
		}
	}
	reply.Data = &v1.GetArticleVerifierTaskDetailsReply_Data{
		Id:                   v.Id,
		Title:                v.Title,
		Content:              v.Content,
		Keyword:              v.Keyword,
		Category:             v.Category,
		ReaderId:             v.ReaderId,
		ReaderAt:             v.ReaderAt,
		PublishAt:            v.PublishAt,
		DeadlineAt:           v.DeadlineAt,
		Editor:               v.Editor,
		EditorCharge:         v.EditorCharge,
		EditorMain:           v.EditorMain,
		PlatformEditor:       v.PlatformEditor,
		PlatformEditorCharge: v.PlatformEditorCharge,
		PlatformEditorMain:   v.PlatformEditorMain,
		SourceUrl:            v.SourceUrl,
		Briefing:             v.Briefing,
		CreatedAt:            v.CreatedAt,
		State:                v.State,
		MediaId:              v.MediaId,
		Media:                v.Media,
		PlatformId:           v.PlatformId,
		Platform:             v.Platform,
		ChannelName:          v.ChannelName,
		MediaIdManual:        v.MediaIdManual,
		ProblemAreaManual:    v.ProblemAreaManual,
		VerifierId:           v.VerifierId,
	}
	articleRecords := &ArticleRecords{}
	articleRecordsLists := make([]*getArticleVerifierTaskDetails, 0)
	err = db.Table(articleRecords.TableName()+" a").
		Joins("LEFT JOIN "+(&User{}).TableName()+" b ON a.reader_id = b.id").
		Where("a.article_id = ? AND a.deleted_at = 0", req.ArticleId).
		Select("a.`id`, a.`article_id`, a.`from_type`, a.`reader_id`, a.`reader_at`, a.`reader_check`, a.`reader_draft`, a.`verifier_id`, a.`error_category`, a.`error_urls`, a.`error_location`, a.`error_type`, a.`error_type_int`, a.`error_subtype`, a.`error_reason`, a.`error_message`, a.`error_modify_advice`, a.`error_level`, a.`error_content`, a.`error_video_screenshot`, a.`reader_score`, a.`verifier_score_at`, a.`misjudged`, a.`confidence_level`, a.`conclusion_type`, a.`conclusion`, a.`created_at`, a.`updated_at`, a.`note`", "COALESCE(b.real_name,'')").
		Find(&articleRecordsLists).
		Error
	if err != nil {
		return nil, err
	}
	reply.Data.Records = make([]*v1.GetArticleVerifierTaskDetailsReply_Data_Records, 0, len(articleRecordsLists))
	recordsIds := make([]int64, 0)
	readerIds := make([]int64, 0)
	for _, w := range articleRecordsLists {
		recordsIds = append(recordsIds, w.Id)
		if w.ReaderId > 0 {
			readerIds = append(readerIds, w.ReaderId)
		}
		tmp := &v1.GetArticleVerifierTaskDetailsReply_Data_Records{
			Id:                   w.Id,
			ArticleId:            w.ArticleId,
			FromType:             w.FromType,
			ReaderId:             w.ReaderId,
			ReaderAt:             w.ReaderAt,
			ReaderDraft:          w.ReaderDraft,
			VerifierId:           w.VerifierId,
			ErrorCategory:        w.ErrorCategory,
			ErrorUrls:            w.ErrorUrls,
			ErrorLocation:        w.ErrorLocation,
			ErrorType:            w.ErrorType,
			ErrorTypeInt:         w.ErrorTypeInt,
			ErrorSubtype:         w.ErrorSubtype,
			ErrorReason:          w.ErrorReason,
			ErrorMessage:         w.ErrorMessage,
			ErrorModifyAdvice:    w.ErrorModifyAdvice,
			ErrorLevel:           w.ErrorLevel,
			ErrorContent:         w.ErrorContent,
			ErrorVideoScreenshot: w.ErrorVideoScreenshot,
			ReaderScore:          w.ReaderScore,
			VerifierScoreAt:      w.VerifierScoreAt,
			Misjudged:            w.Misjudged,
			Note:                 w.Note,
			ConfidenceLevel:      w.ConfidenceLevel,
			ConclusionType:       w.ConclusionType,
			Conclusion:           w.Conclusion,
			CreatedAt:            w.CreatedAt,
			ReaderCheck:          int32(w.ReaderCheck),
		}
		reply.Data.Records = append(reply.Data.Records, tmp)
	}
	usersMap := make(map[int64]*User)
	if len(readerIds) > 0 {
		users := make([]*User, 0)
		db.Model(&User{}).Where("id in (?)", readerIds).Find(&users)
		for _, v := range users {
			usersMap[v.Id] = v
		}
	}
	disposals := make([]*ArticleRecordsDisposal, 0)
	db.Table((&ArticleRecordsDisposal{}).TableName()+" a").
		Joins("LEFT JOIN "+(&User{}).TableName()+" b ON a.user_id = b.id").
		Where("a.article_records_id IN (?)", recordsIds).
		Select("a.id", "a.pid", "a.user_id", "a.article_records_id", "a.content", "a.created_at", "COALESCE(b.real_name,'') as note").
		Find(&disposals)
	disposalsMap := make(map[int64][]*v1.GetArticleVerifierTaskDetailsReply_Data_Records_Disposal, len(disposals))
	for _, w := range disposals {
		if _, ok := disposalsMap[w.ArticleRecordsId]; !ok {
			disposalsMap[w.ArticleRecordsId] = make([]*v1.GetArticleVerifierTaskDetailsReply_Data_Records_Disposal, 0, 1)
		}
		tmp := &v1.GetArticleVerifierTaskDetailsReply_Data_Records_Disposal{
			Id:               w.Id,
			Pid:              w.Pid,
			UserId:           w.UserId,
			ArticleRecordsId: w.ArticleRecordsId,
			Content:          w.Content,
			UserName:         w.Note,
			CreatedAt:        w.CreatedAt,
		}
		disposalsMap[w.ArticleRecordsId] = append(disposalsMap[w.ArticleRecordsId], tmp)
	}
	for _, w := range reply.Data.Records {
		tmp, ok := disposalsMap[w.Id]
		if ok {
			w.Disposal = tmp
		} else {
			w.Disposal = make([]*v1.GetArticleVerifierTaskDetailsReply_Data_Records_Disposal, 0)
		}
		if user, ok := usersMap[w.ReaderId]; ok {
			w.ReaderName = user.RealName
		}
	}
	return reply, nil
}

// postArticleVerifierUnlockArticleAuto 自动解绑稿件与复核员的绑定
func (s *Articles) postArticleVerifierUnlockArticleAuto() {
	db := s.db.DB()
	timestamp := util.Timestamp()
	minutes, err := s.basic.getCfgInt("B11")
	if err != nil {
		s.logger.Log(log.LevelWarn, "error", err.Error())
		return
	}
	if minutes <= 0 {
		minutes = 60
	}
	expired := timestamp - minutes*60
	articles := make([]*Article, 0)
	err = db.Model(&Article{}).
		Where("verifier_id > 0").
		Where("verifier_at < ?", expired).
		Where("state = 17").
		Limit(100).
		Select("id").
		Find(&articles).
		Error
	if err != nil {
		s.logger.Log(log.LevelWarn, "error", err.Error())
		return
	}
	for _, v := range articles {
		msg, err := s.postArticleVerifierUnlockArticle(v.Id, false)
		if err != nil {
			s.logger.Log(log.LevelWarn, "error", err.Error())
		}
		if msg != nil {
			s.logger.Log(log.LevelInfo, "error", msg.Error())
		}
	}
}

func (s *Articles) postArticleVerifierUnlockArticle(articleId int64, mandatory bool) (error, error) {
	db := s.db.DB()
	timestamp := util.Timestamp()
	articles := make([]*Article, 0)
	err := db.Model(&Article{}).Where("id = ?", articleId).Limit(1).Find(&articles).Error
	if err != nil {
		return nil, err
	}
	if len(articles) == 0 {
		return errors.New("稿件信息不存在"), nil
	}
	exist := articles[0]
	if exist.VerifierId == 0 {
		return nil, nil
	}
	if !mandatory {
		records := make([]*ArticleRecords, 0)
		err = db.Model(&ArticleRecords{}).
			Where("article_id = ?", articleId).
			Where("verifier_id > 0").
			Where("reader_draft = 0").
			Where("deleted_at = 0").
			Limit(1).
			Find(&records).
			Error
		if err != nil {
			return nil, err
		}
		if len(records) > 0 {
			// return errors.New("复核员已审核"), nil
			return nil, nil
		}
	}
	_ = db.Transaction(func(tx *gorm.DB) error {
		err = tx.Model(&Article{}).
			Where("id = ?", exist.Id).
			Updates(map[string]any{
				"verifier_id": 0,
				"verifier_at": 0,
				"updated_at":  timestamp,
			}).Error
		if err != nil {
			return err
		}
		if mandatory {
			err = tx.Model(&ArticleRecords{}).
				Where("article_id = ?", exist.Id).
				Updates(map[string]any{
					"verifier_id":       0,
					"reader_score":      0,
					"verifier_score_at": 0,
					"updated_at":        timestamp,
				}).Error
			if err != nil {
				return err
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return nil, nil
}

func (s *Articles) PostArticleVerifierUnlockArticle(ctx context.Context, req *v1.PostArticleVerifierUnlockArticleRequest) (*v1.PostArticleVerifierUnlockArticleReply, error) {
	reply := &v1.PostArticleVerifierUnlockArticleReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	msg, err := s.postArticleVerifierUnlockArticle(req.ArticleId, req.GetMandatory())
	if err != nil {
		return nil, err
	}
	if msg != nil {
		reply.StatusCode = 500
		reply.Message = msg.Error()
		return reply, nil
	}
	return reply, nil
}
func (s *Articles) PostArticleVerifierArticleUpdate(ctx context.Context, req *v1.PostArticleVerifierArticleUpdateRequest) (*v1.PostArticleVerifierArticleUpdateReply, error) {
	reply := &v1.PostArticleVerifierArticleUpdateReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	db := s.db.DB()
	article := &Article{}
	articles := make([]*Article, 0, 1)
	err := db.Model(article).
		WithContext(ctx).
		Where("id = ? AND deleted_at = 0", req.ArticleId).
		Limit(1).
		Find(&articles).Error
	if err != nil {
		return nil, err
	}
	if len(articles) == 0 {
		reply.StatusCode = 500
		reply.Message = "稿件不存在"
		return reply, nil
	}
	if articles[0].State != 17 {
		reply.StatusCode = 500
		reply.Message = "当前无法操作"
		return reply, nil
	}
	updates := make(map[string]any)
	if req.Editor != nil {
		updates["editor"] = *req.Editor
	}
	if req.EditorCharge != nil {
		updates["editor_charge"] = *req.EditorCharge
	}
	if req.EditorMain != nil {
		updates["editor_main"] = *req.EditorMain
	}
	if req.PlatformEditor != nil {
		updates["platform_editor"] = *req.PlatformEditor
	}
	if req.PlatformEditorCharge != nil {
		updates["platform_editor_charge"] = *req.PlatformEditorCharge
	}
	if req.PlatformEditorMain != nil {
		updates["platform_editor_main"] = *req.PlatformEditorMain
	}
	if req.Briefing != nil {
		updates["briefing"] = *req.Briefing
	}
	if req.MediaIdManual != nil {
		updates["media_id_manual"] = *req.MediaIdManual
	}
	if req.ProblemAreaManual != nil {
		updates["problem_area_manual"] = *req.ProblemAreaManual
	}
	if req.Title != nil {
		updates["title"] = *req.Title
	}
	if req.PublishAt != nil {
		updates["publish_at"] = *req.PublishAt
	}
	if req.SourceUrl != nil {
		updates["source_url"] = *req.SourceUrl
	}
	if len(updates) == 0 {
		return reply, nil
	}
	db.Model(article).Where("id = ?", req.ArticleId).Updates(updates)
	return reply, nil
}
func (s *Articles) GetArticleVerifierTaskRecordsList(ctx context.Context, req *v1.GetArticleVerifierTaskRecordsListRequest) (*v1.GetArticleVerifierTaskRecordsListReply, error) {
	reply := &v1.GetArticleVerifierTaskRecordsListReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	db := s.db.DB()
	records := make([]*ArticleRecords, 0)
	err := db.Table((&ArticleRecords{}).TableName()+" a").
		Joins("LEFT JOIN "+(&User{}).TableName()+" b ON a.reader_id = b.id").
		Where("a.article_id = ? AND a.deleted_at = 0", req.ArticleId).
		Select("a.`id`, a.`article_id`, a.`from_type`, a.`reader_id`, a.`reader_at`, a.`reader_check`, a.`reader_draft`, a.`verifier_id`, a.`error_category`, a.`error_urls`, a.`error_location`, a.`error_type`, a.`error_type_int`, a.`error_subtype`, a.`error_reason`, a.`error_message`, a.`error_modify_advice`, a.`error_level`, a.`error_content`, a.`error_video_screenshot`, a.`reader_score`, a.`verifier_score_at`, a.`misjudged`, a.`confidence_level`, a.`conclusion_type`, a.`conclusion`, a.`created_at`, a.`updated_at`, a.`deleted_at`", "COALESCE(b.real_name,'') as note").
		Find(&records).
		Error
	if err != nil {
		return nil, err
	}
	data := make([]*v1.GetArticleVerifierTaskRecordsListReply_Data, 0, len(records))
	for _, v := range records {
		tmp := &v1.GetArticleVerifierTaskRecordsListReply_Data{
			Id:                   v.Id,
			ArticleId:            v.ArticleId,
			FromType:             v.FromType,
			ReaderId:             v.ReaderId,
			ReaderAt:             v.ReaderAt,
			ReaderCheck:          int32(v.ReaderCheck),
			ReaderDraft:          v.ReaderDraft,
			VerifierId:           v.VerifierId,
			ErrorCategory:        v.ErrorCategory,
			ErrorUrls:            v.ErrorUrls,
			ErrorLocation:        v.ErrorLocation,
			ErrorType:            v.ErrorType,
			ErrorTypeInt:         v.ErrorTypeInt,
			ErrorSubtype:         v.ErrorSubtype,
			ErrorReason:          v.ErrorReason,
			ErrorMessage:         v.ErrorMessage,
			ErrorModifyAdvice:    v.ErrorModifyAdvice,
			ErrorLevel:           v.ErrorLevel,
			ErrorContent:         v.ErrorContent,
			ErrorVideoScreenshot: v.ErrorVideoScreenshot,
			ReaderScore:          v.ReaderScore,
			VerifierScoreAt:      v.VerifierScoreAt,
			Misjudged:            v.Misjudged,
			Note:                 v.Note,
			ConfidenceLevel:      v.ConfidenceLevel,
			ConclusionType:       v.ConclusionType,
			Conclusion:           v.Conclusion,
			CreatedAt:            v.CreatedAt,
			ReaderName:           *v.Note,
		}
		data = append(data, tmp)
	}
	reply.Data = data
	return reply, nil
}
func (s *Articles) GetArticleVerifierTaskManualList(ctx context.Context, req *v1.GetArticleVerifierTaskManualListRequest) (*v1.GetArticleVerifierTaskManualListReply, error) {
	reply := &v1.GetArticleVerifierTaskManualListReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	db := s.db.DB()
	table := db.Model(&Article{})
	if req.Profile != nil && *req.Profile {
		table.Where("verifier_id = ?", req.UserId)
		if req.NoVerifier != nil && *req.NoVerifier {
			table.Where("verifier_id = 0")
		}
	} else {
		table.Where("category = 2")
	}
	table.Where("deleted_at = 0")
	if req.PublishAtStart != nil && req.PublishAtEnd != nil && *req.PublishAtStart > 0 && *req.PublishAtEnd > *req.PublishAtStart {
		table.Where("publish_at BETWEEN ? AND ?", *req.PublishAtStart, *req.PublishAtEnd)
	}
	if req.CreatedAtStart != nil && req.CreatedAtEnd != nil && *req.CreatedAtStart > 0 && *req.CreatedAtEnd > *req.CreatedAtStart {
		table.Where("created_at BETWEEN ? AND ?", *req.CreatedAtStart, *req.CreatedAtEnd)
	}
	if req.MediaId != nil || req.PlatformId != nil || req.ChannelName != nil {
		sources := db.Model(&ApproveSources{})
		if req.MediaId != nil {
			sources.Where("media_id = ?", req.GetMediaId())
		}
		if req.PlatformId != nil {
			sources.Where("platform_id = ?", req.GetPlatformId())
		}
		if req.ChannelName != nil {
			sources.Where("channel_name = ?", req.GetChannelName())
		}
		sources.Select("id")
		table.Where("source_id IN (?)", sources)
	}
	if req.Keyword != nil && *req.Keyword != "" {
		table.Where("title LIKE ?", fmt.Sprintf("%%%s%%", *req.Keyword))
	}
	if req.Reader != nil && *req.Reader != "" {
		articleReaders := db.Table((&ArticleReader{}).TableName()+" a").
			Joins("LEFT JOIN "+(&User{}).TableName()+" b ON a.reader_id = b.id").
			Where("b.real_name LIKE ?", fmt.Sprintf("%%%s%%", *req.Reader)).
			Select("a.article_id").
			Group("a.article_id")
		table.Where("id IN (?)", articleReaders)
	}
	if req.TopicId != nil {
		s.likeTopic(req.GetTopicId(), table, "keyword")
	}

	// if req.Profile != nil && *req.Profile {
	// 	ikv := make([]*KeyValueInt32Int64, 0)
	// 	db.Table("(?) a", table.Select("category")).
	// 		Select("category as k", "count(*) as v").
	// 		Group("category").
	// 		Scan(&ikv)
	// 	reply.Categories = make(map[int32]int64)
	// 	for _, v := range ikv {
	// 		reply.Categories[v.K] = v.V
	// 	}
	// } else {
	ikv := make([]*KeyValueInt32Int64, 0)
	db.Table("(?) a", table.Select("state")).
		Select("state as k", "count(*) as v").
		Group("state").
		Scan(&ikv)
	reply.Categories = make(map[int32]int64)
	for _, v := range ikv {
		reply.Categories[v.K] = v.V
	}
	// }

	if req.State != nil {
		table.Where("state = ?", req.State)
	}
	err := table.Select("*").Count(&reply.Total).Error
	if err != nil {
		return nil, err
	}

	articles := make([]*Article, 0)
	page := getPage(req.GetPage())
	limit := getLimit(req.GetLimit())
	offset := getOffset(page, limit)
	table.Order("deadline_at ASC").Limit(limit).Offset(offset)
	table.Select("id", "title", "publish_at", "source_url", "created_at", "source_id", "state", "deadline_at", "media_id_manual", "problem_area_manual", "verifier_id")
	err = table.Find(&articles).Error
	if err != nil {
		return nil, err
	}
	if len(articles) == 0 {
		reply.Data = make([]*v1.GetArticleVerifierTaskManualListReply_Data, 0)
		return reply, nil
	}
	articleIds := make([]int64, 0)
	sourcesIds := make([]int64, 0)
	sourcesIdsMap := make(map[int64]*struct{})
	userId := make([]int64, 0)
	for _, v := range articles {
		articleIds = append(articleIds, v.Id)
		if _, ok := sourcesIdsMap[v.SourceId]; !ok {
			sourcesIds = append(sourcesIds, v.SourceId)
			sourcesIdsMap[v.SourceId] = nil
		}
		if v.VerifierId > 0 {
			userId = append(userId, v.VerifierId)
		}
	}
	sources := make([]*ApproveSources, 0)
	db.Model(&ApproveSources{}).
		Where("id IN (?)", sourcesIds).
		Select("id", "media", "platform", "channel_name").
		Find(&sources)
	sourcesMap := make(map[int64]*ApproveSources)
	for _, v := range sources {
		sourcesMap[v.Id] = v
	}
	articleRecords := make([]*ArticleRecords, 0)
	db.Model(&ArticleRecords{}).
		Where("article_id IN (?) AND deleted_at = 0", articleIds).
		Select("article_id", "count(*) AS id", "COALESCE(sum(reader_score)) as reader_score").
		Group("article_id").
		Find(&articleRecords)
	articleRecordsMap := make(map[int64]*ArticleRecords)
	for _, v := range articleRecords {
		articleRecordsMap[v.ArticleId] = v
	}
	readers := make([]*User, 0)
	db.Table((&ArticleReader{}).TableName()+" a").
		Joins("LEFT JOIN "+(&User{}).TableName()+" b ON a.reader_id = b.id").
		Where("b.id IS NOT NULL").
		Select("a.article_id as id", "b.real_name", "a.reader_at as created_at").
		Find(&readers)
	readersMap := make(map[int64][]string)
	readersMapMap := make(map[int64]map[string]*struct{})
	readersArticleReceiveAtMap := make(map[int64]int64) // 稿件最后领取时间
	for _, v := range readers {
		if _, ok := readersMap[v.Id]; !ok {
			readersMap[v.Id] = make([]string, 0, 1)
			readersMapMap[v.Id] = make(map[string]*struct{}, 1)
		}
		if v.CreatedAt > 0 {
			readersArticleReceiveAtMap[v.Id] = v.CreatedAt
		}
		if _, ok := readersMapMap[v.Id][v.RealName]; ok {
			continue
		}
		readersMap[v.Id] = append(readersMap[v.Id], v.RealName)
		readersMapMap[v.Id][v.RealName] = nil
	}
	mediaMap := make(map[int64]*ApproveSources)
	{
		mediaIds := make([]int64, 0)
		mediaIdsMap := make(map[int64]*struct{})
		for _, v := range articles {
			if _, ok := mediaIdsMap[v.MediaIdManual]; !ok {
				mediaIdsMap[v.MediaIdManual] = nil
				mediaIds = append(mediaIds, v.MediaIdManual)
			}
		}
		if len(mediaIds) > 0 {
			medias := make([]*ApproveSources, 0)
			db.Model(&ApproveSources{}).Where("media_id IN (?)", mediaIds).Find(&medias)
			for _, v := range medias {
				mediaMap[v.MediaId] = v
			}
		}
	}
	usersMap := make(map[int64]*User)
	if len(userId) > 0 {
		users := make([]*User, 0)
		db.Model(&User{}).Where("id IN (?)", userId).Find(&users)
		for _, v := range users {
			usersMap[v.Id] = v
		}
	}

	data := make([]*v1.GetArticleVerifierTaskManualListReply_Data, 0, len(articles))
	for _, v := range articles {
		w := &v1.GetArticleVerifierTaskManualListReply_Data{
			Id:                v.Id,
			Title:             v.Title,
			PublishAt:         v.PublishAt,
			SourceUrl:         v.SourceUrl,
			CreatedAt:         v.CreatedAt,
			MediaIdManual:     v.MediaIdManual,
			ProblemAreaManual: v.ProblemAreaManual,
			State:             v.State,
			DeadlineAt:        v.DeadlineAt,
			VerifierId:        v.VerifierId,
		}
		if tmp, ok := sourcesMap[v.SourceId]; ok {
			w.Media = &tmp.Media
			w.Platform = &tmp.Platform
			w.ChannelName = &tmp.ChannelName
		}
		if tmp, ok := articleRecordsMap[v.Id]; ok {
			w.Records = tmp.Id
			w.TotalScore = int64(tmp.ReaderScore)
		}
		if tmp, ok := readersMap[v.Id]; ok {
			w.Readers = tmp
		}
		if tmp, ok := mediaMap[v.Id]; ok {
			w.Media = &tmp.Media
		}
		if tmp, ok := readersArticleReceiveAtMap[v.Id]; ok {
			w.ReceiveAt = tmp
		}
		if tmp, ok := usersMap[v.VerifierId]; ok {
			w.VerifierRealName = tmp.RealName
		}
		data = append(data, w)
	}
	reply.Data = data
	return reply, nil
}

type getArticleVerifierArticleRecordsListData struct {
	ArticleRecords
	State *int32 `gorm:"column:state"`
}

type articleRecordsDisposalUserRealName struct {
	ArticleRecordsDisposal
	UserRealName string `gorm:"column:user_real_name"`
}

func (s *Articles) GetArticleVerifierArticleRecordsList(ctx context.Context, req *v1.GetArticleVerifierArticleRecordsListRequest) (*v1.GetArticleVerifierArticleRecordsListReply, error) {
	reply := &v1.GetArticleVerifierArticleRecordsListReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	db := s.db.DB()
	records := make([]*getArticleVerifierArticleRecordsListData, 0)
	table := db.Table((&ArticleRecords{}).TableName() + " a").
		Joins("LEFT JOIN " + (&Article{}).TableName() + " b ON a.article_id = b.id").
		WithContext(ctx).
		Where("a.deleted_at = 0")

	if req.Profile != nil && *req.Profile {
		table.Where("b.verifier_id = ?", req.UserId)
	}

	if req.State != nil {
		table.Where("b.state = ?", req.State)
	}
	if req.HasVerifier != nil {
		if *req.HasVerifier {
			table.Where("b.verifier_id > 0")
		} else {
			table.Where("b.verifier_id = 0")
		}
	}
	if req.PublishAtStart != nil && req.PublishAtEnd != nil && *req.PublishAtStart > 0 && *req.PublishAtEnd > *req.PublishAtStart {
		table.Where("b.publish_at BETWEEN ? AND ?", *req.PublishAtStart, *req.PublishAtEnd)
	}
	if req.CreatedAtStart != nil && req.CreatedAtEnd != nil && *req.CreatedAtStart > 0 && *req.CreatedAtEnd > *req.CreatedAtStart {
		table.Where("a.created_at BETWEEN ? AND ?", *req.CreatedAtStart, *req.CreatedAtEnd)
	}
	if req.MediaId != nil || req.PlatformId != nil || req.ChannelName != nil {
		sources := db.Model(&ApproveSources{})
		if req.MediaId != nil {
			sources.Where("media_id = ?", req.GetMediaId())
		}
		if req.PlatformId != nil {
			sources.Where("platform_id = ?", req.GetPlatformId())
		}
		if req.ChannelName != nil {
			sources.Where("channel_name = ?", req.GetChannelName())
		}
		sources.Select("id")
		table.Where("b.source_id IN (?)", sources)
	}
	if req.TextType != nil && *req.TextType != "" {
		table.Where("a.error_category = 1 AND a.error_type = ?", req.GetTextType())
	}
	if req.ImageType != nil && *req.ImageType != "" {
		table.Where("a.error_category = 2 AND a.error_type = ?", req.GetImageType())
	}
	if req.VideoType != nil && *req.VideoType != "" {
		table.Where("a.error_category = 3 AND a.error_type = ?", req.GetVideoType())
	}
	if req.Keyword != nil && *req.Keyword != "" {
		table.Where("b.title LIKE ?", fmt.Sprintf("%%%s%%", *req.Keyword))
	}
	if req.Reader != nil && *req.Reader != "" {
		articleReaders := db.Table((&ArticleReader{}).TableName()+" a").
			Joins("LEFT JOIN "+(&User{}).TableName()+" b ON a.reader_id = b.id").
			Where("b.real_name LIKE ?", fmt.Sprintf("%%%s%%", *req.Reader)).
			Select("a.article_id").
			Group("a.article_id")
		table.Where("b.id IN (?)", articleReaders)
	}
	if req.ErrorLocation != nil {
		table.Where("a.error_location = ?", req.GetErrorLocation())
	}
	if req.ErrorType != nil {
		table.Where("a.error_type = ?", req.GetErrorType())
	}
	if req.TopicId != nil {
		s.likeTopic(req.GetTopicId(), table, "b.keyword")
	}

	ikv := make([]*KeyValueInt32Int64, 0)
	db.Table("(?) a", table.Select("a.from_type")).
		Select("from_type as k", "count(*) as v").
		Group("from_type").
		Scan(&ikv)
	reply.Categories = make(map[int32]int64)
	for _, v := range ikv {
		reply.Categories[v.K] = v.V
	}

	table.Where("a.from_type = ?", req.FromType)

	err := table.Select("*").Count(&reply.Total).Error
	if err != nil {
		return nil, err
	}

	page := getPage(req.GetPage())
	limit := getLimit(req.GetLimit())
	offset := getOffset(page, limit)
	table.Limit(limit).Offset(offset)
	err = table.Order("a.id ASC").
		Select("a.*", "b.state").
		Find(&records).
		Error
	if err != nil {
		return nil, err
	}
	data := make([]*v1.GetArticleVerifierArticleRecordsListReply_Data, 0, len(records))
	articleRecordsIds := make([]int64, 0)
	articleIds := make([]int64, 0)
	userIds := make([]int64, 0)
	for _, v := range records {
		articleRecordsIds = append(articleRecordsIds, v.Id)
		articleIds = append(articleIds, v.ArticleId)
		if v.ReaderId > 0 {
			userIds = append(userIds, v.ReaderId)
		}
		if v.VerifierId > 0 {
			userIds = append(userIds, v.VerifierId)
		}
		tmp := &v1.GetArticleVerifierArticleRecordsListReply_Data{
			Id:                   v.Id,
			ArticleId:            v.ArticleId,
			FromType:             v.FromType,
			ReaderId:             v.ReaderId,
			ReaderAt:             v.ReaderAt,
			ReaderCheck:          int32(v.ReaderCheck),
			ReaderDraft:          v.ReaderDraft,
			VerifierId:           v.VerifierId,
			ErrorCategory:        v.ErrorCategory,
			ErrorUrls:            v.ErrorUrls,
			ErrorLocation:        v.ErrorLocation,
			ErrorType:            v.ErrorType,
			ErrorTypeInt:         v.ErrorTypeInt,
			ErrorSubtype:         v.ErrorSubtype,
			ErrorReason:          v.ErrorReason,
			ErrorMessage:         v.ErrorMessage,
			ErrorModifyAdvice:    v.ErrorModifyAdvice,
			ErrorLevel:           v.ErrorLevel,
			ErrorContent:         v.ErrorContent,
			ErrorVideoScreenshot: v.ErrorVideoScreenshot,
			ReaderScore:          v.ReaderScore,
			VerifierScoreAt:      v.VerifierScoreAt,
			Misjudged:            v.Misjudged,
			Note:                 v.Note,
			ConfidenceLevel:      v.ConfidenceLevel,
			ConclusionType:       v.ConclusionType,
			Conclusion:           v.Conclusion,
			CreatedAt:            v.CreatedAt,
			State:                v.State,
		}
		data = append(data, tmp)
	}

	users := make([]*User, 0)
	usersMap := make(map[int64]*User)
	if len(userIds) > 0 {
		db.Model(&User{}).Where("id IN (?)", userIds).Select("id", "real_name").Find(&users)
		for _, v := range users {
			usersMap[v.Id] = v
		}
	}

	disposals := make([]*articleRecordsDisposalUserRealName, 0)
	disposalsMap := make(map[int64][]*articleRecordsDisposalUserRealName)
	if len(articleRecordsIds) > 0 {
		db.Table((&ArticleRecordsDisposal{}).TableName()+" a").
			Joins("LEFT JOIN "+(&User{}).TableName()+" b ON a.user_id = b.id").
			Where("a.article_records_id IN (?)", articleRecordsIds).
			Select("a.*", "COALESCE(b.real_name,'') as user_real_name").
			Find(&disposals)
		for _, v := range disposals {
			if _, ok := disposalsMap[v.ArticleRecordsId]; !ok {
				disposalsMap[v.ArticleRecordsId] = make([]*articleRecordsDisposalUserRealName, 0, 1)
			}
			disposalsMap[v.ArticleRecordsId] = append(disposalsMap[v.ArticleRecordsId], v)
		}
	}

	attach := make([]*ApproveSources, 0)
	db.Table((&Article{}).TableName()+" a").
		Joins("LEFT JOIN "+(&ApproveSources{}).TableName()+" b ON a.source_id = b.id").
		Where("a.id IN (?)", articleIds).
		Select("a.id as id", "a.title as source_label", "a.publish_at as created_at", "b.media_id", "b.media", "b.platform_id", "b.platform", "b.channel_name").
		Find(&attach)
	attachMap := make(map[int64]*ApproveSources)
	for _, v := range attach {
		attachMap[v.Id] = v
	}
	attach1 := make([]*ApproveSources, 0)
	db.Table((&Article{}).TableName()+" a").
		Joins("LEFT JOIN "+(&ApproveSources{}).TableName()+" b ON a.media_id_manual = b.media_id").
		Where("a.id IN (?)", articleIds).
		Select("a.id as id", "a.title as source_label", "a.publish_at as created_at", "b.media_id", "b.media").
		Find(&attach1)
	attachMap1 := make(map[int64]*ApproveSources)
	for _, v := range attach {
		attachMap1[v.Id] = v
	}
	for _, v := range data {
		tmp, ok := attachMap[v.ArticleId]
		if ok {
			v.Media = &tmp.Media
			v.Platform = &tmp.Platform
			v.ChannelName = &tmp.ChannelName
			v.Title = *tmp.SourceLabel
			v.PublishAt = tmp.CreatedAt
		}
		tmp1, ok := attachMap1[v.ArticleId]
		if ok {
			v.Media = &tmp1.Media
		}
		if has, ok := usersMap[v.ReaderId]; ok && has != nil {
			v.ReaderRealName = has.RealName
		}
		if has, ok := usersMap[v.VerifierId]; ok && has != nil {
			v.VerifierRealName = has.RealName
		}
		if has, ok := disposalsMap[v.Id]; ok && has != nil {
			for _, w := range has {
				v.Disposal = append(v.Disposal, &v1.GetArticleVerifierArticleRecordsListReply_Data_Disposal{
					Id:               w.Id,
					Pid:              w.Pid,
					UserId:           w.UserId,
					ArticleRecordsId: w.ArticleRecordsId,
					Content:          w.Content,
					Note:             w.Note,
					CreatedAt:        w.CreatedAt,
					UserRealName:     w.UserRealName,
				})
			}
		}
	}
	reply.Data = data
	return reply, nil
}
func (s *Articles) scoreVerify(records []*ArticleRecords) (err error) {
	if len(records) == 0 {
		return nil
	}
	scoreMinMachine, scoreMaxMachine := int64(0), int64(0)
	scoreMinManuals, scoreMaxManuals := int64(0), int64(0)
	scoreMinMachine, err = s.basic.getCfgInt("B0D")
	if err != nil {
		return
	}
	scoreMaxMachine, err = s.basic.getCfgInt("B0E")
	if err != nil {
		return
	}
	scoreMinManuals, err = s.basic.getCfgInt("B0F")
	if err != nil {
		return
	}
	scoreMaxManuals, err = s.basic.getCfgInt("B10")
	if err != nil {
		return
	}
	if scoreMinMachine < 0 || scoreMaxMachine < scoreMinMachine {
		return errors.New("系统配置机审分值区间不合法,请联系管理员")
	}
	if scoreMinManuals < 0 || scoreMaxManuals < scoreMinManuals {
		return errors.New("系统配置人审分值区间不合法,请联系管理员")
	}
	for _, v := range records {
		switch v.FromType {
		case 1:
			if int64(v.ReaderScore) < scoreMinMachine || int64(v.ReaderScore) > scoreMaxMachine {
				return fmt.Errorf("分值不在[%d,%d]区间内", scoreMinMachine, scoreMaxMachine)
			}
		case 2, 3:
			if int64(v.ReaderScore) < scoreMinManuals || int64(v.ReaderScore) > scoreMaxManuals {
				return fmt.Errorf("分值不在[%d,%d]区间内", scoreMinManuals, scoreMaxManuals)
			}
		default:
			return fmt.Errorf("未知的差错来源类型")
		}
	}
	return nil
}
func (s *Articles) PostArticleVerifierManualRecordsUpdate(ctx context.Context, req *v1.PostArticleVerifierManualRecordsUpdateRequest) (*v1.PostArticleVerifierManualRecordsUpdateReply, error) {
	reply := &v1.PostArticleVerifierManualRecordsUpdateReply{}
	reply.StatusCode = 200
	reply.Message = "success"

	db := s.db.DB()
	timestamp := util.Timestamp()

	if id := req.GetId(); id > 0 {
		records := make([]*ArticleRecords, 0)
		temp := db.Model(&ArticleRecords{}).
			WithContext(ctx).
			Where("id = ?", req.Id)
		if req.GetArticleId() > 0 {
			temp.Where("article_id = ?", req.GetArticleId())
		}
		err := temp.Limit(1).Find(&records).Error
		if err != nil {
			return nil, err
		}
		if len(records) == 0 {
			reply.StatusCode = 500
			reply.Message = "差错记录不存在"
			return reply, nil
		}
		u := make(map[string]any)
		if req.ReaderCheck != nil {
			u["reader_check"] = *req.ReaderCheck
		}
		if req.ErrorUrls != nil {
			u["error_urls"] = *req.ErrorUrls
		}
		if req.ErrorLocation != nil {
			u["error_location"] = *req.ErrorLocation
		}
		if req.ErrorType != nil {
			u["error_type"] = *req.ErrorType
		}
		if req.ErrorTypeInt != nil {
			u["error_type_int"] = *req.ErrorTypeInt
		}
		if req.ErrorSubtype != nil {
			u["error_subtype"] = *req.ErrorSubtype
		}
		if req.ErrorReason != nil {
			u["error_reason"] = *req.ErrorReason
		}
		if req.ErrorMessage != nil {
			u["error_message"] = *req.ErrorMessage
		}
		if req.ErrorModifyAdvice != nil {
			u["error_modify_advice"] = *req.ErrorModifyAdvice
		}
		if req.ErrorLevel != nil {
			u["error_level"] = *req.ErrorLevel
		}
		if req.ErrorContent != nil {
			u["error_content"] = *req.ErrorContent
		}
		if req.ErrorVideoScreenshot != nil {
			u["error_video_screenshot"] = *req.ErrorVideoScreenshot
		}
		if req.Misjudged != nil {
			u["misjudged"] = *req.Misjudged
		}
		if req.ConfidenceLevel != nil {
			u["confidence_level"] = *req.ConfidenceLevel
		}
		if req.ConclusionType != nil {
			u["conclusion_type"] = *req.ConclusionType
		}
		if req.Conclusion != nil {
			u["conclusion"] = *req.Conclusion
		}
		if req.Note != nil {
			u["note"] = *req.Note
		}
		if req.ReaderScore != nil {
			for _, v := range records {
				v.ReaderScore = *req.ReaderScore
				v.Misjudged = req.GetMisjudged()
			}
			u["reader_score"] = *req.ReaderScore
			if err = s.scoreVerify(records); err != nil {
				reply.StatusCode = 500
				reply.Message = err.Error()
				return reply, nil
			}
		}
		if len(u) > 0 {
			u["updated_at"] = timestamp
			u["verifier_id"] = req.UserId
			err = db.Model(&ArticleRecords{}).Where("id = ?", req.Id).Updates(u).Error
			if err != nil {
				return nil, err
			}
		}
	}
	articleId := req.GetArticleId()
	if articleId > 0 {
		w := map[string]any{}
		if req.PlatformEditor != nil {
			w["platform_editor"] = req.GetPlatformEditor()
		}
		if req.PlatformEditorCharge != nil {
			w["platform_editor_charge"] = req.GetPlatformEditorCharge()
		}
		if req.PlatformEditorMain != nil {
			w["platform_editor_main"] = req.GetPlatformEditorMain()
		}
		if len(w) > 0 {
			w["updated_at"] = timestamp
		}
		err := db.Model(&Article{}).Where("id = ?", articleId).Updates(w).Error
		if err != nil {
			return nil, err
		}
	}
	return reply, nil
}
func (s *Articles) PostArticleVerifierScoreUpdate(ctx context.Context, req *v1.PostArticleVerifierScoreUpdateRequest) (*v1.PostArticleVerifierScoreUpdateReply, error) {
	reply := &v1.PostArticleVerifierScoreUpdateReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	db := s.db.DB()
	recordsIdList := make([]int64, 0)
	for _, v := range req.Lists {
		recordsIdList = append(recordsIdList, v.Id)
	}
	records := make([]*ArticleRecords, 0)
	err := db.Model(&ArticleRecords{}).
		WithContext(ctx).
		// Where("article_id = ? AND id IN (?)", req.ArticleId, recordsIdList).
		Where("id IN (?)", recordsIdList).
		Find(&records).
		Error
	if err != nil {
		return nil, err
	}
	if len(records) == 0 {
		return reply, nil
	}
	recordsMap := make(map[int64]*ArticleRecords)
	for _, v := range records {
		recordsMap[v.Id] = v
	}
	timestamp := util.Timestamp()
	usingRecords := make([]*ArticleRecords, 0)
	for _, v := range req.Lists {
		tmp, ok := recordsMap[v.Id]
		if ok {
			tmp.ReaderScore = v.ReaderScore
			tmp.Misjudged = v.Misjudged
			tmp.VerifierScoreAt = timestamp
			usingRecords = append(usingRecords, tmp)
		}
	}
	if err = s.scoreVerify(usingRecords); err != nil {
		return nil, err
	}
	_ = db.Transaction(func(tx *gorm.DB) error {
		for _, v := range usingRecords {
			err = tx.Model(&ArticleRecords{}).Where("id = ?", v.Id).Updates(map[string]any{
				"reader_score":      v.ReaderScore,
				"verifier_score_at": timestamp,
				"misjudged":         v.Misjudged,
				"updated_at":        v.Misjudged,
			}).Error
			if err != nil {
				return err
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return reply, nil
}
func (s *Articles) PostArticleVerifierClosedUpdate(ctx context.Context, req *v1.PostArticleVerifierClosedUpdateRequest) (*v1.PostArticleVerifierClosedUpdateReply, error) {
	reply := &v1.PostArticleVerifierClosedUpdateReply{}
	reply.StatusCode = 200
	reply.Message = "success"

	db := s.db.DB()
	articles := make([]*Article, 0)
	err := db.Model(&Article{}).
		WithContext(ctx).
		Where("id = ?", req.ArticleId).
		Find(&articles).
		Error
	if err != nil {
		return nil, err
	}
	if len(articles) == 0 {
		reply.StatusCode = 500
		reply.Message = "稿件记录不存在"
		return reply, nil
	}
	if articles[0].State != 17 {
		reply.StatusCode = 500
		reply.Message = "当前状态不是待复核状态,无法结案"
		return reply, nil
	}
	// 更新审读员积分的数据
	records := make([]*ArticleRecords, 0)
	err = db.Model(&ArticleRecords{}).
		WithContext(ctx).
		Where("article_id = ? AND reader_score <> 0", req.ArticleId).
		Find(&records).
		Error
	if err != nil {
		return nil, err
	}
	users := make([]*User, 0)
	usersMap := make(map[int64]*User)
	if len(records) > 0 {
		userIds := make([]int64, 0, 1)
		userIdsMap := make(map[int64]*struct{})
		for _, v := range records {
			if _, ok := userIdsMap[v.ReaderId]; !ok {
				userIds = append(userIds, v.ReaderId)
				userIdsMap[v.ReaderId] = nil
			}
		}
		if len(userIds) > 0 {
			err = db.Model(&User{}).Where("id IN (?)", userIds).Find(&users).Error
			if err != nil {
				return nil, err
			}
			for _, v := range users {
				usersMap[v.Id] = v
			}
		}
	}

	timestamp := util.Timestamp()

	length := len(records)
	// 构建审更新读员积分的数据
	integralMap := make(map[int64][]*UserIntegral, length)
	integralNext := make(map[int64]int64, length)
	userMap := make(map[int64]*User, length)
	for _, v := range records {
		if v.ReaderScore == 0 {
			continue
		}
		user, ok := usersMap[v.ReaderId]
		if !ok {
			continue
		}
		if _, ok = integralMap[user.Id]; !ok {
			integralMap[user.Id] = make([]*UserIntegral, 0, 1)
			integralNext[user.Id] = user.Integral
			userMap[user.Id] = user
		}
		integral := &UserIntegral{
			Category:     1,
			ChangeBefore: integralNext[user.Id],
			ChangeAmount: int64(v.ReaderScore),
			State:        9,
			StateDesc:    "已完成",
		}
		integral.UserId = v.ReaderId
		integral.AssocId = v.Id
		integral.ChangeAfter = integral.ChangeBefore + integral.ChangeAmount
		integral.CreatedAt = timestamp
		integral.UpdatedAt = timestamp

		integralMap[user.Id] = append(integralMap[user.Id], integral)
		integralNext[user.Id] = integral.ChangeAfter
	}

	_ = db.Transaction(func(tx *gorm.DB) error {
		v := make(map[string]any)
		v["state"] = 25
		v["claimed"] = 2
		v["updated_at"] = timestamp
		err = tx.Model(&Article{}).Where("id = ?", req.ArticleId).Updates(v).Error
		if err != nil {
			return err
		}
		w := make(map[string]any)
		w["state"] = 6
		w["updated_at"] = timestamp
		err = tx.Model(&ArticleReader{}).Where("article_id = ? AND state IN ( 2, 4 )", req.ArticleId).Updates(w).Error
		if err != nil {
			return err
		}
		// 操作审读员积分
		for _, user := range userMap {
			err = s.updateUserIntegral(
				context.WithValue(context.Background(), ArticleClosedUpdateUserIntegral, true),
				tx,
				user,
				integralMap[user.Id]...,
			)
			if err != nil {
				return err
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	// 尝试冻结误判的审读员
	go s.freeze(req.ArticleId)
	return reply, nil
}
func (s *Articles) PostArticleVerifierRecordsDisposalCreate(ctx context.Context, req *v1.PostArticleVerifierRecordsDisposalCreateRequest) (*v1.PostArticleVerifierRecordsDisposalCreateReply, error) {
	reply := &v1.PostArticleVerifierRecordsDisposalCreateReply{}
	reply.StatusCode = 200
	reply.Message = "success"

	disposal := req.Content
	disposal = strings.TrimSpace(disposal)
	if disposal == "" {
		reply.StatusCode = 500
		reply.Message = "处置内容为空"
		return reply, nil
	}

	db := s.db.DB()
	records := make([]*ArticleRecords, 0)
	err := db.Model(&ArticleRecords{}).
		Select("id").Where("id = ?", req.GetArticleRecordsId()).
		Limit(1).
		Find(&records).
		Error
	if err != nil {
		return nil, err
	}
	if len(records) == 0 {
		reply.StatusCode = 500
		reply.Message = "差错id不正确"
		return reply, nil
	}
	timestamp := util.Timestamp()
	create := &ArticleRecordsDisposal{}
	create.UserId = req.GetUserId()
	create.ArticleRecordsId = req.GetArticleRecordsId()
	create.Content = &disposal
	create.CreatedAt = timestamp
	create.UpdatedAt = timestamp
	err = db.Model(&ArticleRecordsDisposal{}).
		WithContext(ctx).
		Create(create).
		Error
	if err != nil {
		return nil, err
	}
	return reply, nil
}
func (s *Articles) GetThisMonthScore(ctx context.Context, req *v1.GetThisMonthScoreRequest) (*v1.GetThisMonthScoreReply, error) {
	reply := &v1.GetThisMonthScoreReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	reply.Data = &v1.GetThisMonthScoreReply_Data{}
	db := s.db.DB()
	lists := make([]*RunValue, 0)
	db.Model(&RunValue{}).
		WithContext(ctx).
		Where("run_key = ?", ThisMonthScore).
		Limit(1).
		Find(&lists)
	if len(lists) > 0 {
		reply.Data.ThisMonthScores = lists[0].RunValInt
	}
	return reply, nil
}
func (s *Articles) GetMonthStatistics(ctx context.Context, req *v1.GetMonthStatisticsRequest) (*v1.GetMonthStatisticsReply, error) {
	reply := &v1.GetMonthStatisticsReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	db := s.db.DB()
	table := db.Model(&User{})
	fc := func(db *gorm.DB, column string) {
		if req.AtStart != nil && req.AtEnd != nil && *req.AtEnd > *req.AtStart && *req.AtStart > 0 {
			table.Where(fmt.Sprintf("%s BETWEEN ? AND ?", column), *req.AtStart, *req.AtEnd)
		}
	}
	err := table.Count(&reply.Total).Error
	if err != nil {
		return nil, err
	}
	if reply.Total == 0 {
		reply.Data = make([]*v1.GetMonthStatisticsReply_Data, 0)
		return reply, nil
	}
	page := getPage(req.GetPage())
	limit := getLimit(req.GetLimit())
	offset := getOffset(page, limit)
	table.Order("id DESC").Limit(limit).Offset(offset)
	table.Select("id", "real_name", "mobile", "note")
	lists := make([]*User, 0)
	err = table.Find(&lists).Error
	if err != nil {
		return nil, err
	}
	length := len(lists)
	mp := make(map[int64]*v1.GetMonthStatisticsReply_Data, length)
	ids := make([]int64, 0, length)
	reply.Data = make([]*v1.GetMonthStatisticsReply_Data, 0, length)
	for _, v := range lists {
		tmp := &v1.GetMonthStatisticsReply_Data{
			Id:       v.Id,
			RealName: v.RealName,
			Mobile:   v.Mobile,
			Note:     v.Note,
		}
		mp[v.Id] = tmp
		ids = append(ids, v.Id)
		reply.Data = append(reply.Data, tmp)
	}
	wg := &sync.WaitGroup{}
	mu := &sync.Mutex{}
	wg.Add(1)
	go func() {
		defer wg.Done()
		// 统计领取稿件的数量
		all := make([]*ArticleReader, 0)
		s1 := db.Model(&ArticleReader{})
		fc(s1, "created_at")
		s1.Where("reader_id IN(?)", ids)
		s1.Group("reader_id")
		s1.Select("count(*) as id", "reader_id")
		s1.Find(&all)
		mu.Lock()
		defer mu.Unlock()
		for _, v := range all {
			tmp, ok := mp[v.ReaderId]
			if !ok {
				continue
			}
			tmp.Claimed = v.Id
		}
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		// 统计已审稿件的数量
		all := make([]*ArticleReader, 0)
		s1 := db.Model(&ArticleReader{})
		fc(s1, "created_at")
		s1.Where("reader_id IN(?)", ids)
		s1.Where("state IN (2,4,6)")
		s1.Group("reader_id")
		s1.Select("count(*) as id", "reader_id")
		s1.Find(&all)
		mu.Lock()
		defer mu.Unlock()
		for _, v := range all {
			tmp, ok := mp[v.ReaderId]
			if !ok {
				continue
			}
			tmp.Read = v.Id
		}
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		// 统计已审稿件的数量
		all := make([]*ArticleReader, 0)
		s1 := db.Model(&ArticleReader{})
		fc(s1, "created_at")
		s1.Where("reader_id IN(?)", ids)
		s1.Where("state IN (5)")
		s1.Group("reader_id")
		s1.Select("count(*) as id", "reader_id")
		s1.Find(&all)
		mu.Lock()
		defer mu.Unlock()
		for _, v := range all {
			tmp, ok := mp[v.ReaderId]
			if !ok {
				continue
			}
			tmp.TimedOut = v.Id
		}
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		// 发现差错稿件数量
		all := make([]*ArticleRecords, 0)
		s1 := db.Model(&ArticleRecords{})
		fc(s1, "created_at")
		s1.Where("reader_id IN(?)", ids)
		s1.Where("reader_draft = 0")
		s1.Where("(( from_type = 1 AND reader_check = 1 ) OR from_type IN (2,3) )", ids)
		s1.Group("article_id, reader_id")
		s1.Select("article_id, reader_id")
		s1 = db.Table("(?) a").
			Group("reader_id").
			Select("count(*) as id", "reader_id")
		s1.Find(&all)
		mu.Lock()
		defer mu.Unlock()
		for _, v := range all {
			tmp, ok := mp[v.ReaderId]
			if !ok {
				continue
			}
			tmp.FindError = v.Id
		}
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		// 差错误判稿件数量
		all := make([]*ArticleRecords, 0)
		s1 := db.Model(&ArticleRecords{})
		fc(s1, "created_at")
		s1.Where("reader_id IN(?)", ids)
		s1.Where("reader_draft = 0")
		s1.Where("misjudged = 1", ids)
		s1.Group("article_id, reader_id")
		s1.Select("article_id, reader_id")
		s1 = db.Table("(?) a").
			Group("reader_id").
			Select("count(*) as id", "reader_id")
		s1.Find(&all)
		mu.Lock()
		defer mu.Unlock()
		for _, v := range all {
			tmp, ok := mp[v.ReaderId]
			if !ok {
				continue
			}
			tmp.Misjudged = v.Id
		}
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		// 有效判定稿件数量
		all := make([]*ArticleReader, 0)
		s1 := db.Model(&ArticleReader{})
		fc(s1, "created_at")
		s1.Where("reader_id IN (?)", ids)
		s1.Where("state = 6")
		s1.Group("reader_id")
		s1.Select("count(*) as id", "reader_id")
		s1.Find(&all)
		mu.Lock()
		defer mu.Unlock()
		for _, v := range all {
			tmp, ok := mp[v.ReaderId]
			if !ok {
				continue
			}
			tmp.Effective = v.Id
		}
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		// 合计得分(得分,扣分 会中和)
		all := make([]*UserIntegral, 0)
		s1 := db.Model(&UserIntegral{})
		fc(s1, "created_at")
		s1.Where("user_id IN(?)", ids)
		s1.Where("category = 1")
		s1.Where("state = 9")
		s1.Group("user_id")
		s1.Select("COALESCE(sum(change_amount),0) as id", "user_id")
		s1.Find(&all)
		mu.Lock()
		defer mu.Unlock()
		for _, v := range all {
			tmp, ok := mp[v.UserId]
			if !ok {
				continue
			}
			tmp.TotalScore = v.Id
		}
	}()
	wg.Wait()
	return reply, nil
}

type getMonthSettleReplyData struct {
	// id
	Id int64 `gorm:"column:id"`
	// 姓名
	RealName *string `gorm:"column:real_name"`
	// 手机号
	Mobile *string `gorm:"column:mobile"`
	// 当前积分余额
	Integral int64 `gorm:"column:integral"`
	// 可提现金额
	AllowAmount int64 `gorm:"column:allow_amount"`
	// 申请提现金额
	ApplyAmount int64 `gorm:"column:apply_amount"`
	// 剩余积分
	RemainingAmount int64 `gorm:"column:remaining_amount"`
	// 申请提现时间
	CreatedAt int64 `gorm:"column:created_at"`
	// 备注
	Note string `gorm:"column:note"`
	// 状态
	State int32 `gorm:"column:state"`
}

func (s *Articles) GetMonthSettle(ctx context.Context, req *v1.GetMonthSettleRequest) (*v1.GetMonthSettleReply, error) {
	reply := &v1.GetMonthSettleReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	db := s.db.DB()
	table := db.Table((&UserWithdraw{}).TableName() + " a").
		WithContext(ctx).
		Joins("LEFT JOIN " + (&User{}).TableName() + " b ON a.user_id = b.id")
	if req.State != nil {
		table.Where("a.state = ?", req.State)
	}
	if req.AtStart != nil && req.AtEnd != nil && *req.AtEnd > *req.AtStart && *req.AtStart > 0 {
		table.Where("a.created_at BETWEEN ? AND ?", *req.AtStart, *req.AtEnd)
	}
	err := table.Count(&reply.Total).Error
	if err != nil {
		return nil, err
	}
	if reply.Total == 0 {
		reply.Data = make([]*v1.GetMonthSettleReply_Data, 0)
		return reply, nil
	}
	page := getPage(req.GetPage())
	limit := getLimit(req.GetLimit())
	offset := getOffset(page, limit)
	table.Order("id DESC").Limit(limit).Offset(offset)
	table.Select(
		"a.id",
		"b.mobile",
		"b.integral",
		"a.change_amount as apply_amount",
		"a.change_after as remaining_amount",
		"a.created_at",
		"a.note",
		"a.state",
	)
	lists := make([]*getMonthSettleReplyData, 0)
	err = table.Find(&lists).Error
	if err != nil {
		return nil, err
	}
	reply.Data = make([]*v1.GetMonthSettleReply_Data, 0, len(lists))
	for _, v := range lists {
		tmp := &v1.GetMonthSettleReply_Data{
			Id:              v.Id,
			Mobile:          v.Mobile,
			Integral:        v.Integral,
			AllowAmount:     v.AllowAmount,
			ApplyAmount:     v.ApplyAmount,
			RemainingAmount: v.RemainingAmount,
			CreatedAt:       v.CreatedAt,
			Note:            v.Note,
			State:           v.State,
		}
		if v.RealName != nil {
			tmp.RealName = *v.RealName
		}
		reply.Data = append(reply.Data, tmp)
	}
	return reply, nil
}
func (s *Articles) PostMonthSettleApproval(ctx context.Context, req *v1.PostMonthSettleApprovalRequest) (*v1.PostMonthSettleApprovalReply, error) {
	reply := &v1.PostMonthSettleApprovalReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	if req.State != 5 && req.State != 9 {
		reply.StatusCode = 500
		reply.Message = "非预期意见值"
		return reply, nil
	}
	db := s.db.DB()
	lists := make([]*UserWithdraw, 0)
	err := db.Model(&UserWithdraw{}).Where("id = ?", req.Id).Limit(1).Find(&lists).Error
	if err != nil {
		return nil, err
	}
	if len(lists) == 0 {
		reply.StatusCode = 500
		reply.Message = "记录不存在"
		return reply, nil
	}
	first := lists[0]
	if first.State != 1 {
		reply.StatusCode = 500
		reply.Message = "状态异常"
		return reply, nil
	}
	users := make([]*User, 0)
	err = db.Model(&User{}).Where("id = ?", first.UserId).Limit(1).Find(&users).Error
	if err != nil {
		return nil, err
	}
	if len(users) == 0 {
		reply.StatusCode = 500
		reply.Message = "提现账户不存在"
		return reply, nil
	}
	user := users[0]
	timestamp := util.Timestamp()
	_ = db.Transaction(func(tx *gorm.DB) error {
		modify := map[string]any{
			"state":      req.State,
			"updated_at": timestamp,
		}
		if req.Note != nil && *req.Note != "" {
			modify["note"] = *req.Note
		}
		err = tx.Model(&UserWithdraw{}).Where("id = ?", first.Id).Updates(modify).Error
		if err != nil {
			return err
		}
		if req.State == 5 && first.ChangeAmount != 0 {
			// 审批不通过 退回用户的积分
			integral := &UserIntegral{
				Category:     2,
				AssocId:      first.Id,
				ChangeBefore: user.Integral,
				ChangeAmount: 0 - first.ChangeAmount,
				ChangeAfter:  user.Integral - first.ChangeAmount,
				State:        9,
				StateDesc:    "已完成",
			}
			integral.UserId = first.UserId
			integral.AssocId = first.Id
			integral.Note = "提现审批不通过退回"
			integral.CreatedAt = timestamp
			integral.UpdatedAt = timestamp
			err = s.updateUserIntegral(context.Background(), tx, user, integral)
			if err != nil {
				return err
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return reply, nil
}

func (s *Articles) GetArticleVerifierWorkbench1(ctx context.Context, req *v1.GetArticleVerifierWorkbench1Request) (*v1.GetArticleVerifierWorkbench1Reply, error) {
	reply := &v1.GetArticleVerifierWorkbench1Reply{}
	reply.StatusCode = 200
	reply.Message = "success"
	reply.Data = &v1.GetArticleVerifierWorkbench1Reply_Data{}
	db := s.db.DB()
	wg := &sync.WaitGroup{}
	wg.Add(1)
	go func() {
		defer wg.Done()
		now := time.Now()
		endAt := now.Unix()
		startAt := endAt - 86400
		table := db.Model(&ArticleReader{}).
			Where("created_at BETWEEN ? AND ?", startAt, endAt).
			Where("deleted_at = 0").
			Group("article_id").
			Select("article_id")
		db.Table("(?) a", table).Count(&reply.Data.Reader24Hours)
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		now := time.Now()
		endAt := now.Unix()
		startAt := endAt - 7*86400
		table := db.Model(&ArticleReader{}).
			Where("created_at BETWEEN ? AND ?", startAt, endAt).
			Where("deleted_at = 0").
			Group("article_id").
			Select("article_id")
		db.Table("(?) a", table).Count(&reply.Data.Reader7Days)
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		now := time.Now()
		endAt := now.Unix()
		startAt := endAt - 30*86400
		table := db.Model(&ArticleReader{}).
			Where("created_at BETWEEN ? AND ?", startAt, endAt).
			Where("deleted_at = 0").
			Group("article_id").
			Select("article_id")
		db.Table("(?) a", table).Count(&reply.Data.Reader30Days)
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		now := time.Now()
		endAt := now.Unix()
		startAt := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location()).Unix()
		table := db.Model(&ArticleReader{}).
			Where("created_at BETWEEN ? AND ?", startAt, endAt).
			Where("deleted_at = 0").
			Group("article_id").
			Select("article_id")
		db.Table("(?) a", table).Count(&reply.Data.ReaderThisYear)
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		now := time.Now()
		endAt := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location()).Unix() - 1
		startAt := time.Date(now.Year()-1, 1, 1, 0, 0, 0, 0, now.Location()).Unix()
		table := db.Model(&ArticleReader{}).
			Where("created_at BETWEEN ? AND ?", startAt, endAt).
			Where("deleted_at = 0").
			Group("article_id").
			Select("article_id")
		db.Table("(?) a", table).Count(&reply.Data.ReaderLastYear)
	}()
	wg.Wait()
	return reply, nil
}
func (s *Articles) GetArticleVerifierWorkbench2(ctx context.Context, req *v1.GetArticleVerifierWorkbench2Request) (*v1.GetArticleVerifierWorkbench2Reply, error) {
	reply := &v1.GetArticleVerifierWorkbench2Reply{}
	reply.StatusCode = 200
	reply.Message = "success"
	data := make([]*v1.GetArticleVerifierWorkbench2Reply_Data, 0)
	now := time.Now()
	startAt, endAt := int64(0), int64(0)

	db := s.db.DB()
	article := &Article{}
	get1 := db.Model(article)
	get2 := db.Model(article)

	category := req.GetCategory()
	switch category {
	case 2: // 本月
		start := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
		startAt = start.Unix()
		endAt = now.Unix()
		get1.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%d') AS time_at")
		get2.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%d') AS time_at")
	case 3: // 近七日
		start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
		startAt = start.Unix() - 6*86400
		endAt = now.Unix()
		get1.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%d') AS time_at")
		get2.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%d') AS time_at")
	default: // 本年
		start := time.Date(now.Year(), time.January, 1, 0, 0, 0, 0, now.Location())
		startAt = start.Unix()
		endAt = now.Unix()
		get1.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%m') AS time_at")
		get2.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%m') AS time_at")
	}
	get1.Where("created_at BETWEEN ? AND ?", startAt, endAt)
	get2.Where("created_at BETWEEN ? AND ?", startAt, endAt)

	// 查询已经领取过的所有稿件
	get1.Where("state IN (9,17,25,33)")
	// 查询已经结案的所有稿件
	get2.Where("state = 25")

	get1.Where("deleted_at = 0")
	get2.Where("deleted_at = 0")

	lists1 := make([]*readerWorkbench2, 0)
	lists2 := make([]*readerWorkbench2, 0)
	db.Table("(?) a", get1).Select("COUNT(*) as counts", "time_at").Group("time_at").Find(&lists1)
	db.Table("(?) a", get2).Select("COUNT(*) as counts", "time_at").Group("time_at").Find(&lists2)

	map1 := make(map[string]*readerWorkbench2)
	map2 := make(map[string]*readerWorkbench2)
	for _, v := range lists1 {
		map1[v.TimeAt] = v
	}
	for _, v := range lists2 {
		map2[v.TimeAt] = v
	}

	switch category {
	case 2:
		for i := 1; i <= now.Day(); i++ {
			data = append(data, &v1.GetArticleVerifierWorkbench2Reply_Data{
				Time: fmt.Sprintf("%d", i),
			})
		}
	case 3:
		for i := startAt; ; i += 86400 {
			if i > endAt {
				break
			}
			t := time.Unix(i, 0)
			data = append(data, &v1.GetArticleVerifierWorkbench2Reply_Data{
				Time: fmt.Sprintf("%d", t.Day()),
			})
		}
	default:
		for i := 1; i <= 12; i++ {
			data = append(data, &v1.GetArticleVerifierWorkbench2Reply_Data{
				Time: fmt.Sprintf("%d", i),
			})
		}
	}

	for _, v := range data {
		completed, ok := map2[v.Time]
		if ok {
			v.Closed = completed.Count
		}
		claimed, ok := map1[v.Time]
		if ok {
			v.Read = claimed.Count
		}
	}
	reply.Data = data
	return reply, nil
}
func (s *Articles) GetArticleVerifierWorkbench3(ctx context.Context, req *v1.GetArticleVerifierWorkbench3Request) (*v1.GetArticleVerifierWorkbench3Reply, error) {
	reply := &v1.GetArticleVerifierWorkbench3Reply{}
	reply.StatusCode = 200
	reply.Message = "success"
	data := make([]*v1.GetArticleVerifierWorkbench3Reply_Data, 0)
	now := time.Now()
	startAt, endAt := int64(0), int64(0)

	db := s.db.DB()
	articleReader := &ArticleReader{}
	get1 := db.Model(articleReader)

	category := req.GetCategory()
	switch category {
	case 2: // 本月
		start := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
		startAt = start.Unix()
		endAt = now.Unix()
	case 3: // 近七日
		start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
		startAt = start.Unix() - 6*86400
		endAt = now.Unix()
	default: // 本年
		start := time.Date(now.Year(), time.January, 1, 0, 0, 0, 0, now.Location())
		startAt = start.Unix()
		endAt = now.Unix()
	}
	get1.Where("created_at BETWEEN ? AND ?", startAt, endAt)

	get1.Where("deleted_at = 0")
	get1.Select("reader_id", "article_id")
	get1.Group("reader_id,article_id")

	lists := make([]*User, 0)
	table := db.Table("(?) a", get1).
		Select("COUNT(*) as counts", "reader_id").
		Group("reader_id")
	db.Table("(?) a", table).
		Joins("LEFT JOIN "+(&User{}).TableName()+" b ON a.reader_id = b.id").
		Where("b.id is not null").
		Select("a.counts as created_at", "a.reader_id as id", "COALESCE(b.real_name,b.username,'') as real_name").
		Order("a.counts DESC").
		Limit(10).
		Find(&lists)
	for _, v := range lists {
		tmp := &v1.GetArticleVerifierWorkbench3Reply_Data{
			Name:     v.RealName,
			Articles: v.CreatedAt,
		}
		data = append(data, tmp)
	}
	reply.Data = data
	return reply, nil
}
func (s *Articles) GetArticleVerifierWorkbench4(ctx context.Context, req *v1.GetArticleVerifierWorkbench4Request) (*v1.GetArticleVerifierWorkbench4Reply, error) {
	reply := &v1.GetArticleVerifierWorkbench4Reply{}
	reply.StatusCode = 200
	reply.Message = "success"
	reply.Data = &v1.GetArticleVerifierWorkbench4Reply_Data{}
	db := s.db.DB()
	wg := &sync.WaitGroup{}
	wg.Add(1)
	go func() {
		defer wg.Done()
		now := time.Now()
		endAt := now.Unix()
		startAt := endAt - 86400
		table := db.Model(&ArticleRecords{}).
			Where("created_at BETWEEN ? AND ?", startAt, endAt).
			Group("article_id").
			Select("article_id")
		db.Table("(?) a", table).Count(&reply.Data.Records24Hours)
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		now := time.Now()
		endAt := now.Unix()
		startAt := endAt - 7*86400
		table := db.Model(&ArticleRecords{}).
			Where("created_at BETWEEN ? AND ?", startAt, endAt).
			Group("article_id").
			Select("article_id")
		db.Table("(?) a", table).Count(&reply.Data.Records7Days)
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		now := time.Now()
		endAt := now.Unix()
		startAt := endAt - 30*86400
		table := db.Model(&ArticleRecords{}).
			Where("created_at BETWEEN ? AND ?", startAt, endAt).
			Group("article_id").
			Select("article_id")
		db.Table("(?) a", table).Count(&reply.Data.Records30Days)
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		now := time.Now()
		endAt := now.Unix()
		startAt := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location()).Unix()
		table := db.Model(&ArticleRecords{}).
			Where("created_at BETWEEN ? AND ?", startAt, endAt).
			Group("article_id").
			Select("article_id")
		db.Table("(?) a", table).Count(&reply.Data.RecordsThisYear)
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		now := time.Now()
		endAt := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location()).Unix() - 1
		startAt := time.Date(now.Year()-1, 1, 1, 0, 0, 0, 0, now.Location()).Unix()
		table := db.Model(&ArticleRecords{}).
			Where("created_at BETWEEN ? AND ?", startAt, endAt).
			Group("article_id").
			Select("article_id")
		db.Table("(?) a", table).Count(&reply.Data.RecordsLastYear)
	}()
	wg.Wait()
	return reply, nil
}
func (s *Articles) GetArticleVerifierWorkbench5(ctx context.Context, req *v1.GetArticleVerifierWorkbench5Request) (*v1.GetArticleVerifierWorkbench5Reply, error) {
	reply := &v1.GetArticleVerifierWorkbench5Reply{}
	reply.StatusCode = 200
	reply.Message = "success"
	data := make([]*v1.GetArticleVerifierWorkbench5Reply_Data, 0)
	now := time.Now()
	startAt, endAt := int64(0), int64(0)

	db := s.db.DB()
	articleRecords := &ArticleRecords{}
	get1 := db.Model(articleRecords)
	get2 := db.Model(articleRecords)

	category := req.GetCategory()
	switch category {
	case 2: // 本月
		start := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
		startAt = start.Unix()
		endAt = now.Unix()
		get1.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%d') AS time_at")
		get2.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%d') AS time_at")
	case 3: // 近七日
		start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
		startAt = start.Unix() - 6*86400
		endAt = now.Unix()
		get1.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%d') AS time_at")
		get2.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%d') AS time_at")
	default: // 本年
		start := time.Date(now.Year(), time.January, 1, 0, 0, 0, 0, now.Location())
		startAt = start.Unix()
		endAt = now.Unix()
		get1.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%m') AS time_at")
		get2.Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%m') AS time_at")
	}
	get1.Where("created_at BETWEEN ? AND ?", startAt, endAt)
	get2.Where("created_at BETWEEN ? AND ?", startAt, endAt)

	get1.Where("misjudged = 1")

	get1.Where("deleted_at = 0")
	get2.Where("deleted_at = 0")

	lists1 := make([]*readerWorkbench2, 0)
	lists2 := make([]*readerWorkbench2, 0)
	db.Table("(?) a", get1).Select("COUNT(*) as counts", "time_at").Group("time_at").Find(&lists1)
	db.Table("(?) a", get2).Select("COUNT(*) as counts", "time_at").Group("time_at").Find(&lists2)

	map1 := make(map[string]*readerWorkbench2)
	map2 := make(map[string]*readerWorkbench2)
	for _, v := range lists1 {
		map1[v.TimeAt] = v
	}
	for _, v := range lists2 {
		map2[v.TimeAt] = v
	}

	switch category {
	case 2:
		for i := 1; i <= now.Day(); i++ {
			data = append(data, &v1.GetArticleVerifierWorkbench5Reply_Data{
				Time: fmt.Sprintf("%d", i),
			})
		}
	case 3:
		for i := startAt; ; i += 86400 {
			if i > endAt {
				break
			}
			t := time.Unix(i, 0)
			data = append(data, &v1.GetArticleVerifierWorkbench5Reply_Data{
				Time: fmt.Sprintf("%d", t.Day()),
			})
		}
	default:
		for i := 1; i <= 12; i++ {
			data = append(data, &v1.GetArticleVerifierWorkbench5Reply_Data{
				Time: fmt.Sprintf("%d", i),
			})
		}
	}

	for _, v := range data {
		completed, ok := map2[v.Time]
		if ok {
			v.Records = completed.Count
		}
		claimed, ok := map1[v.Time]
		if ok {
			v.Misjudged = claimed.Count
		}
	}
	reply.Data = data
	return reply, nil
}
func (s *Articles) GetArticleVerifierWorkbench6(ctx context.Context, req *v1.GetArticleVerifierWorkbench6Request) (*v1.GetArticleVerifierWorkbench6Reply, error) {
	reply := &v1.GetArticleVerifierWorkbench6Reply{}
	reply.StatusCode = 200
	reply.Message = "success"
	data := make([]*v1.GetArticleVerifierWorkbench6Reply_Data, 0)
	now := time.Now()
	startAt, endAt := int64(0), int64(0)

	db := s.db.DB()
	articleRecords := &ArticleRecords{}
	get1 := db.Model(articleRecords)

	category := req.GetCategory()
	switch category {
	case 2: // 本月
		start := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
		startAt = start.Unix()
		endAt = now.Unix()
	case 3: // 近七日
		start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
		startAt = start.Unix() - 6*86400
		endAt = now.Unix()
	default: // 本年
		start := time.Date(now.Year(), time.January, 1, 0, 0, 0, 0, now.Location())
		startAt = start.Unix()
		endAt = now.Unix()
	}
	get1.Where("created_at BETWEEN ? AND ?", startAt, endAt)

	get1.Where("deleted_at = 0")
	get1.Select("reader_id", "article_id")
	get1.Group("reader_id,article_id")

	table := db.Table("(?) a", get1).
		Select("COUNT(*) as counts", "reader_id").
		Group("reader_id")

	lists := make([]*User, 0)
	db.Table("(?) a", table).
		Joins("LEFT JOIN "+(&User{}).TableName()+" b ON a.reader_id = b.id").
		Where("b.id is not null").
		Select("a.counts as created_at", "a.reader_id as id", "COALESCE(b.real_name,b.username,'') as real_name").
		Order("a.counts DESC").
		Limit(10).
		Find(&lists)
	for _, v := range lists {
		tmp := &v1.GetArticleVerifierWorkbench6Reply_Data{
			Name:     v.RealName,
			Articles: v.CreatedAt,
		}
		data = append(data, tmp)
	}
	reply.Data = data
	return reply, nil
}
